目錄
併發網絡通信模型
常見模型分類
- 循環服務器模型 :循環接收客戶端請求,處理請求。同一時刻只能處理一個請求,處理完畢後再處理下一個。(就是TCP 和 UDP基本示例程序)
優點:實現簡單,佔用資源少
缺點:無法同時處理多個客戶端請求
適用情況:處理的任務可以很快完成,客戶端無需長期佔用服務端程序。udp比tcp更適合循環。
- IO併發模型:利用IO多路複用,異步IO等技術,同時處理多個客戶端IO請求。
優點 : 資源消耗少,能同時高效處理多個IO行爲
缺點 : 只能處理併發產生的IO事件,無法處理cpu計算
適用情況:HTTP請求,網絡傳輸等都是IO行爲。
- 多進程/線程網絡併發模型:每當一個客戶端連接服務器,就創建一個新的進程/線程爲該客戶端服務,客戶端退出時再銷燬該進程/線程。
優點:能同時滿足多個客戶端長期佔有服務端需求,可以處理各種請求
缺點: 資源消耗較大
適用情況:客戶端同時連接量較少,需要處理行爲較複雜情況。
基於fork的多進程網絡併發模型
實現步驟
- 創建監聽套接字
- 等待接收客戶端請求
- 客戶端連接創建新的進程處理客戶端請求
- 原進程繼續等待其他客戶端連接
- 如果客戶端退出,則銷燬對應的進程
重點代碼 (fork_server.py):
from socket import *
import os, sys
import signal
def handle(c):
print("客戶端:", c.getpeername())
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.send(b'OK')
c.close()
# 創建監聽套接字
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
s = socket() # tcp套接字
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 設置端口立即重用
s.bind(ADDR)
s.listen(3)
# 殭屍進程處理
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
print("Listen the port 8888...")
# 循環等待客戶端連接
while True:
try:
c, addr = s.accept()
except KeyboardInterrupt:
sys.exit('服務器退出')
except Exception as e:
print(e)
continue
# 創建子進程處理客戶端請求
pid = os.fork()
if pid == 0:
s.close() # 子進程不需要s
handle(c) # 具體處理客戶端請求
os._exit(0)
# 父進程其實只用來處理客戶端連接
else:
c.close() # 父進程不需要c
基於threading的多線程網絡併發模型
實現步驟
- 創建監聽套接字
- 循環接收客戶端連接請求
- 當有新的客戶端連接創建線程處理客戶端請求
- 主線程繼續等待其他客戶端連接
- 當客戶端退出,則對應分支線程退出
重點代碼(thread_server.py):
from socket import *
from threading import Thread
import sys
# 客戶端處理
def handle(c):
print("客戶端:", c.getpeername())
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.send(b'OK')
c.close()
# 創建監聽套接字
HOST = '0.0.0.0'
PORT = 8888
ADDR = (HOST, PORT)
s = socket() # tcp套接字
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 設置端口立即重用
s.bind(ADDR)
s.listen(3)
# 循環等待客戶端連接
while True:
try:
c, addr = s.accept()
except KeyboardInterrupt:
sys.exit('服務器退出')
except Exception as e:
print(e)
continue
# 創建新的線程處理客戶端請求
t = Thread(target=handle, args=(c,))
t.setDaemon(True) # 分支線程隨主線程退出(這句話可加可不加)
t.start()
基於multiprocessing的多進程網絡併發模型
實現步驟(實現步驟與“基於fork的多進程網絡併發模型”實現步驟相同)
- 創建監聽套接字
- 等待接收客戶端請求
- 客戶端連接創建新的進程處理客戶端請求
- 原進程繼續等待其他客戶端連接
- 如果客戶端退出,則銷燬對應的進程
重點代碼(multi_server.py):
from socket import *
from multiprocessing import Process
import sys, signal
# 客戶端處理
def handle(c):
print("客戶端:", c.getpeername())
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.send(b'OK')
c.close()
# 創建監聽套接字
HOST = '0.0.0.0'
PORT = 8888
ADDR = (HOST, PORT)
s = socket() # tcp套接字
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 設置端口立即重用
s.bind(ADDR)
s.listen(3)
# 殭屍進程處理
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
# 循環等待客戶端連接
while True:
try:
c, addr = s.accept()
except KeyboardInterrupt:
sys.exit('服務器退出')
except Exception as e:
print(e)
continue
# 創建新的線程處理客戶端請求
p = Process(target=handle, args=(c,))
p.daemon = True # 子進程隨父進程退出
p.start()
擴展:集成模塊完成多進程/多線程網併發
這部分感興趣自己看一下
-
import sockectserver
通過模塊提供的不同的類的組合來完成多進程或者多線程,tcp 或udp的網路併發模型 -
常用類說明
TCPServer:創建TCP服務端套接字
UDPServer:創建UDP服務端套接字StreamRequestHandler:處理TCP客戶端請求
DatageramRequestHandler:處理udp客戶端請求ForkingMinIn:創建多進程併發
ForkingTCPServer:ForkingMinIn + TCPServer
ForkingUDPServer:ForkingMinIn + UDPServerThreadingMixIn:創建多線程併發
ThreadingTCPServer :ThreadingMixIn + TCPServer
ThreadingUDPServer:ThreadingMixIn + UDPServer -
步驟
【1】創建服務器類,通過選擇繼承的類,決定創建TCP或者UDP,多進程或者多線程確定定法類型
【2】創建請求處理類,根據服務類型選擇stream處理類還是Datager處理類。重寫handle方法,做具體的請求處理
【3】通過服務器類創建服務器對象,並綁定請求處理類
【4】通過富強武器對象,調用server_forever()啓動服務
ftp 文件服務器
功能:
【1】 分爲服務端和客戶端,要求可以有多個客戶端同時操作。
【2】 客戶端可以查看服務器文件庫中有什麼文件。
【3】 客戶端可以從文件庫中下載文件到本地。
【4】 客戶端可以上傳一個本地文件到文件庫。
【5】 使用print在客戶端打印命令輸入提示,引導操作。
ftp文件服務器思路分析:
- 技術點分析
- 併發模型:多線程併發模式
- 數據傳輸:tcp傳輸
- 結構設計
- 客戶端發起請求,打印請求提示界面
- 文件傳輸功能封裝爲類
- 功能分析
- 網絡搭建
- 查看文件庫信息
- 下載文件
- 上傳文件
- 客戶端退出
- 協議
- L–表示請求文件列表
- Q–表示退出
- G–表示下載
- P–表示上傳
IO併發
只針對IO行爲
IO分類
IO分類:阻塞IO ,非阻塞IO,IO多路複用,異步IO等
阻塞IO
- 定義:在執行IO操作時如果執行條件不滿足則阻塞。阻塞IO是IO的默認形態。
- 效率:阻塞IO是效率很低的一種IO。但是由於邏輯簡單所以是默認IO行爲。
- 阻塞情況:(佔用較少CPU)
- 因爲某種執行條件沒有滿足造成的函數阻塞
e.g. accept input recv - 處理IO的時間較長產生的阻塞狀態
e.g. 網絡傳輸,大文件讀寫
非阻塞IO
定義 :通過修改IO屬性行爲,使原本阻塞(因爲某種執行條件沒有滿足造成的函數阻塞)的IO變爲非阻塞的狀態。
- 設置套接字爲非阻塞IO
sockfd.setblocking(bool)
功能:設置套接字爲非阻塞IO
參數:默認爲True,表示套接字IO阻塞;設置爲False則套接字IO變爲非阻塞
- 超時檢測 :設置一個最長阻塞時間,超過該時間後則不再阻塞等待。
sockfd.settimeout(sec)
功能:設置套接字的超時時間
參數:設置的時間
重點代碼:
from socket import *
from time import sleep, ctime
f = open('log.txt', 'a+')
# tcp 套接字
sockfd = socket()
sockfd.bind(('127.0.0.1', 8888))
sockfd.listen(3)
# 設置套接字爲非阻塞
# sockfd.setblocking(False) # 2s 寫一條日誌
# 超時檢測
sockfd.settimeout(3) # 3+2 S 寫一條日誌
while True:
print("Waiting for connect...")
try:
connfd, addr = sockfd.accept()
except (BlockingIOError, timeout) as e:
# 每隔2s寫入一條日誌
sleep(2)
f.write("%s: %s\n" % (ctime(), e))
f.flush()
else:
data = connfd.recv(1024).decode()
print(data)
IO多路複用
-
定義
同時監控多個IO事件,當哪個IO事件準備就緒就執行哪個IO事件。以此形成可以同時處理多個IO的行爲,避免一個IO阻塞造成其他IO均無法執行,提高了IO執行效率。 -
具體方案
- select方法 : windows linux unix
- poll方法: linux unix
- epoll方法: linux
select方法
from select import select
rs, ws, xs = select(rlist, wlist, xlist[, timeout])
功能:監控IO事件,阻塞等待IO發生
參數:rlist 列表 存放關注的等待發生(被動發生的)的IO事件(例如:套接字等待連接accept)
wlist 列表 存放關注的要主動處理的IO事件(例如:TCP中的send)
xlist 列表 存放關注的出現異常要處理的IO(基本不用)
timeout 超時時間(可以無參)
返回值: rs 列表 rlist中準備就緒(已經發生的瞬間事件)的IO
ws 列表 wlist中準備就緒的IO
xs 列表 xlist中準備就緒的IO
示例代碼:
from select import select
from socket import *
# 做幾個IO用作監控
s = socket()
s.bind(('0.0.0.0', 8888))
s.listen(3)
fd = open('log.txt', 'a+')
print("開始提交監控的IO")
rs, ws, xs = select([s], [fd], [])
print("rs:", rs)
print("ws:", ws)
print("xs:", xs)
select 實現tcp服務
【1】將關注的IO放入對應的監控類別列表
【2】通過select函數進行監控
【3】遍歷select返回值列表,確定就緒IO事件
【4】處理髮生的IO事件
注意:
- wlist中如果存在IO事件,則select立即返回給ws
- 處理IO過程中不要出現死循環佔有服務端的情況
- IO多路複用消耗資源較少,效率較高
重點代碼(select_server.py):
from socket import *
from select import select
# 創建一個監聽套接字作爲關注的IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 設置端口可以重用
s.bind(('0.0.0.0', 8888))
s.listen(5)
# 設置關注列表
rlist = [s]
wlist = []
xlist = []
while True:
# 循環監控IO的發生
rs, ws, xs = select(rlist, wlist, xlist)
# 遍歷三個返回列表,判斷哪個IO發生
for r in rs:
# 如果是套接字就緒則處理連接
if r is s:
c, addr = r.accept()
print("Connect from", addr)
rlist.append(c) # 增加新的關注IO事件
# else爲客戶端套接字就緒情況
else: # r is c
data = r.recv(1024)
# 客戶端退出
if not data:
rlist.remove(r) # 從關注列表移除
r.close()
continue # 繼續處理其他就緒IO
print("Receive:", data.decode())
# r.send(b'OK')
# 我們希望主動處理這個IO對象
wlist.append(r)
for w in ws:
w.send(b'OK')
wlist.remove(w) # 使用後移除
for x in xs:
pass
@@擴展:位運算
- 定義:將整數轉換爲二進制,按二進制位進行運算
- 運算符號:
& 按位與
| 按位或
^ 按位異或
<< 左移
>> 右移
e.g. 14 --> 01110
19 --> 10011
14 & 19 = 00010 = 2 一0則0
14 | 19 = 11111 = 31 一1則1
14 ^ 19 = 11101 = 29 相同爲0不同爲1
14 << 2 = 111000 = 56 向左移動低位補0
14 >> 2 = 11 = 3 向右移動去掉低位
poll方法
p = select.poll()
功能 : 創建poll對象
返回值: poll對象
p.register(fd,event)
功能: 註冊關注的IO事件
參數:fd 要關注的IO
event 要關注的IO事件類型
常用類型:POLLIN 讀IO事件(rlist)
POLLOUT 寫IO事件 (wlist)
POLLERR 異常IO (xlist)
POLLHUP 斷開連接
e.g. p.register(sockfd,POLLIN|POLLERR) 同時關注多個事件
p.unregister(fd)
功能:取消對IO的關注
參數:IO對象或者IO對象的fileno
events = p.poll()
功能: 阻塞等待監控的IO事件發生
返回值: 返回發生的IO
events格式 [(fileno,event),()....]
每個元組爲一個就緒IO,元組第一項是該IO的fileno,第二項爲該IO就緒的事件類型
poll_server 步驟
【1】創建套接字
【2】將套接字register
【3】創建查找字典,並維護
【4】循環監控IO發生
【5】處理髮生的IO
次重點代碼(poll_server.py):
from socket import *
from select import *
# 設置套接字爲關注IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 8888))
s.listen(5)
# 創建poll對象關注s
p = poll()
# 建立查找字典{fileno:io_obj},用於通過文件描述符fileno查找IO對象
fdmap = {s.fileno(): s}
# 設置關注IO
p.register(s, POLLIN | POLLERR)
# 循環監控IO事件發生
while True:
events = p.poll() # 阻塞等待IO發生
# 循環遍歷發生的事件 fd-->fileno
for fd, event in events:
# 區分事件進行處理
if fd == s.fileno():
c, addr = fdmap[fd].accept()
print("Connect from", addr)
# 添加新的關注IO
p.register(c, POLLIN | POLLERR)
fdmap[c.fileno()] = c # 維護字典
# elif event & POLLHUP: # 客戶端斷開
# print("客戶端退出")
# p.unregister(fd) # 取消關注
# fdmap[fd].close()
# del fdmap[fd] # 從字典刪除
elif event & POLLIN: # 客戶端發消息
data = fdmap[fd].recv(1024)
# 斷開(POLLERR)發生時data得到空,此時POLLIN也會就緒
if not data:
p.unregister(fd) # 取消關注
fdmap[fd].close()
del fdmap[fd] # 從字典刪除
continue
print("Receive:", data.decode())
fdmap[fd].send(b'OK')
epoll方法
-
使用方法 : 基本與poll相同
生成對象改爲 epoll()
將所有事件類型改爲EPOLL類型 -
epoll特點:
epoll 效率比select、poll要高
epoll 監控IO數量比select要多
epoll 的觸發方式比poll要多 (EPOLLET邊緣觸發)
次重點代碼(epoll_server.py):
from socket import *
from select import *
# 創建套接字
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 8888))
s.listen(3)
# 創建epoll對象關注s
ep = epoll()
# 建立查找字典,用於通過fileno查找IO對象
fdmap = {s.fileno(): s}
# 關注s
ep.register(s, EPOLLIN | EPOLLERR)
# 循環監控
while True:
events = ep.poll()
print(events)
# 循環遍歷發生的事件 fd-->fileno
for fd, event in events:
# 區分事件進行處理
if fd == s.fileno():
c, addr = fdmap[fd].accept()
print("Connect from", addr)
# 添加新的關注IO
# 將觸發方式變爲邊緣觸發(EPOLLET)
ep.register(c, EPOLLIN | EPOLLERR | EPOLLET)
fdmap[c.fileno()] = c # 維護字典
# # 按位與判定是EPOLLIN就緒
# elif event & EPOLLIN:
# data = fdmap[fd].recv(1024)
# if not data:
# ep.unregister(fd) # 取消關注
# fdmap[fd].close()
# del fdmap[fd] # 從字典中刪除
# continue
# print("Receive:", data.decode())
# fdmap[fd].send(b'OK')
協程技術
基礎概念
- 定義:纖程,微線程。是爲非搶佔式多任務產生子程序的計算機組件。協程允許不同入口點在不同位置暫停或開始,簡單來說,協程就是可以暫停執行的函數。(什麼是協程:在應用層通過函數間的暫停跳轉實現多任務同時操作,消耗較少的資源)
- 協程原理 : 記錄一個函數的上下文棧幀,協程調度切換時會將記錄的上下文保存,在切換回來時進行調取,恢復原有的執行內容,以便從上一次執行位置繼續執行。
- 協程優缺點
優點: 【1】協程完成多任務佔用計算資源很少 【2】由於協程的多任務切換在應用層完成,因此切換開銷少 【3】協程爲單線程程序,無需進行共享資源同步互斥處理 缺點:協程的本質是一個單線程,無法利用計算機多核資源
擴展延伸@標準庫協程的實現
python3.5以後,使用標準庫asyncio和async/await 語法來編寫併發代碼。asyncio庫通過對異步IO行爲的支持完成python的協程。
- 同步是指完成事務的邏輯,先執行第一個事務,如果阻塞了,會一直等待,直到這個事務完成,再執行第二個事務,順序執行
- 異步和同步是相對的,異步是指在處理調用這個事務的之後,不會等待這個事務的處理結果,直接處理第二個事務去了,通過狀態、通知、回調來通知調用者處理結果。
雖然官方說asyncio是未來的開發方向,但是由於其生態不夠豐富,大量的客戶端不支持awaitable需要自己去封裝,所以在使用上存在缺陷。更多時候只能使用已有的異步庫(asyncio等),功能有限
程序實現(async_test.py 瞭解即可):
import asyncio
import time
now = lambda: time.time()
async def do_work(x):
print("Waiting:", x)
await asyncio.sleep(x) # 阻塞自動跳轉(無法使用日常的IO阻塞性爲,所以該標準庫日常中幾乎不使用)
return "Done after %s s" % x
start = now()
# 生成協程對象
cor1 = do_work(1)
cor2 = do_work(2)
cor3 = do_work(3)
# 將協程對象生成一個可輪尋操作的對象列表
tasks = [
asyncio.ensure_future(cor1),
asyncio.ensure_future(cor2),
asyncio.ensure_future(cor3)
]
# 得到輪尋對象調用run啓動協程執行
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print("Time:", now() - start)
第三方協程模塊
- greenlet模塊
- 安裝 : sudo pip3 install greenlet
- 函數
greenlet.greenlet(func) 功能:創建協程對象 參數:協程函數 g.switch() 功能:選擇要執行的協程函數
實現代碼(greenlet_test.py):
from greenlet import greenlet
def test1():
print("執行test1")
gr2.switch()
print("結束test1")
gr2.switch()
def test2():
print("執行test2")
gr1.switch()
print("結束test2")
# 將函數變成協程
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch() # 選擇執行協程1
# 返回結果
'''
執行test1
執行test2
結束test1
結束test2
'''
- gevent模塊
- 安裝:sudo pip3 install gevent
- 函數
gevent.spawn(func,argv) 功能: 生成協程對象 參數:func 協程函數 argv 給協程函數傳參(不定參) 返回值: 協程對象 gevent.joinall(list,[timeout]) 功能: 阻塞等待協程執行完畢 參數:list 協程對象列表 timeout 超時時間 gevent.sleep(sec) 功能: gevent睡眠阻塞 參數:睡眠時間
* gevent協程只有在遇到gevent指定的阻塞行爲時纔會自動在協程之間進行跳轉,如:gevent.joinall(),gevent.sleep()帶來的阻塞
實現代碼(gevent_test.py):
import gevent
# 協程函數
def foo(a,b):
print("Running foo ...",a,b)
gevent.sleep(2)
print("Foo again")
def bar():
print("Running bar ...")
gevent.sleep(3)
print("bar again")
# 將函數封裝爲協程,遇到gevent阻塞自動執行
f = gevent.spawn(foo,1,2)
b = gevent.spawn(bar)
gevent.joinall([f,b]) # 阻塞等待[]中的協成結束
- monkey腳本
作用:在gevent協程中,協程只有遇到gevent指定類型的阻塞才能跳轉到其他協程,因此,我們希望將普通的IO阻塞行爲轉換爲可以觸發gevent協程跳轉的阻塞,以提高執行效率。
轉換方法:gevent 提供了一個腳本程序monkey,可以修改底層解釋IO阻塞的行爲,將很多普通阻塞轉換爲gevent阻塞。
使用方法:
【1】導入monkey
from gevent import monkey
【2】運行相應的腳本,例如轉換socket中所有阻塞
monkey.patch_socket()
【3】 如果將所有可轉換的IO阻塞全部轉換則運行all
monkey.patch_all()
【4】注意:腳本運行函數需要在對應模塊導入前執行
實現代碼(gevent_server.py):
import gevent
from gevent import monkey
monkey.patch_all() # 該句執行在導入socket前
from socket import *
# 處理客戶端請求
def handle(c):
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.send(b'OK')
c.close()
# 創建TCP套接字
s = socket()
s.bind(('0.0.0.0', 8888))
s.listen(5)
while True:
c, addr = s.accept()
print("Connect from", addr)
# handle(c) # 循環方案
gevent.spawn(handle, c) # 協程方案
s.close()
HTTPServer v2.0
-
主要功能 :
【1】 接收客戶端(瀏覽器)請求
【2】 解析客戶端發送的請求
【3】 根據請求組織數據內容
【4】 將數據內容形參http響應格式返回給瀏覽器 -
升級點 :
【1】 採用IO併發,可以滿足多個客戶端同時發起請求情況
【2】 做基本的請求解析,根據具體請求返回具體內容,同時滿足客戶端簡單的非網頁請求情況
【3】 通過類接口形式進行功能封裝
httpserver 2.0
技術點:
【1】使用tcp通信
【2】select io多路複用
結構:採用類封裝
類的接口設計:
【1】在用戶使用角度進行工作流程設計
【2】儘可能提供全面的功能,能爲用戶決定的在類中實現
【3】不能替用戶決定的變量可以通過實例化對象傳入類中
【4】不能替用戶決定的複雜功能,可以通過重寫讓用戶自己決定
程序實現(httpserver.py):
from socket import *
from select import select
# 將具體http server功能封裝
class HTTPServer:
def __init__(self, server_address, static_dir):
# 添加屬性
self.server_address = server_address
self.static_dir = static_dir
self.rlist = []
self.wlist = []
self.xlist = []
self.create_socket()
self.bind()
# 創建套接字
def create_socket(self):
self.sockfd = socket()
self.sockfd.setsockopt(SOL_SOCKET,
SO_REUSEADDR, 1)
def bind(self):
self.sockfd.bind(self.server_address)
self.ip = self.server_address[0]
self.port = self.server_address[1]
# 啓動服務
def serve_forever(self):
self.sockfd.listen(5)
print("Listen the port %d" % self.port)
self.rlist.append(self.sockfd)
while True:
rs, ws, xs = select(self.rlist, self.wlist,
self.xlist)
for r in rs:
if r is self.sockfd:
c, addr = r.accept()
print("Connect from", addr)
self.rlist.append(c)
else:
# 處理瀏覽器請求
self.handle(r)
# 具體處理請求
def handle(self, connfd):
# 接收http請求
request = connfd.recv(4096)
# 防止客戶端斷開
if not request:
self.rlist.remove(connfd)
connfd.close()
return
# 請求解析
request_line = request.splitlines()[0]
info = request_line.decode().split(' ')[1]
print(connfd.getpeername(), ":", info)
# info分爲訪問網頁或者其他內容
if info == '/' or info[-5:] == '.html':
self.get_html(connfd, info)
else:
self.get_data(connfd, info)
self.rlist.remove(connfd)
connfd.close()
# 處理網頁請求
def get_html(self, connfd, info):
if info == '/':
filename = self.static_dir + "/index.html"
else:
filename = self.static_dir + info
try:
fd = open(filename)
except Exception:
# 沒有網頁
responseHeaders = "HTTP/1.1 404 Not Found\r\n"
responseHeaders += '\r\n'
responseBody = "Sorry,Not found the page"
else:
# 存在網頁
responseHeaders = "HTTP/1.1 200 OK\r\n"
responseHeaders += '\r\n'
responseBody = fd.read()
finally:
response = responseHeaders + responseBody
connfd.send(response.encode())
# 其他情況
def get_data(self, connfd, info):
responseHeaders = "HTTP/1.1 200 OK\r\n"
responseHeaders += '\r\n'
responseBody = "Waiting for httpserver 3.0"
response = responseHeaders + responseBody
connfd.send(response.encode())
if __name__ == "__main__":
"""
希望通過HTTPServer類快速搭建http服務
用以展示自己的網頁
"""
# 用戶自己決定的內容:地址、內容
server_addr = ('0.0.0.0', 8000) # 服務器地址
static_dir = './static' # 網頁存放位置
httpd = HTTPServer(server_addr, static_dir) # 生成實例對象
httpd.serve_forever() # 啓動http服務