爲了更好地瞭解IO模型,咱們須要事先回顧下:同步、異步、阻塞、非阻塞1.網絡傳輸中的兩個階段 分別是 waitdata 和 copydata send---copydata recv---waitdata + copydata 記住這兩點很重要,由於這些IO模型的區別就是在兩個階段上各有不一樣的狀況。2.阻塞IO 不管是線程 進程 仍是線程 進程池 通通都是阻塞IO 應用程序 發送 系統調用---操做系統等待數據(wait)---數據準備好 return data 因此,blocking IO的特色就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了。3.非阻塞IO 協程是一種非阻塞IO server.setblocking(False)將阻塞修改成非阻塞 最直接體現 recv send accept 都不會阻塞 會當即執行 可是不能保證立馬就有數據 沒有數據拋出異常 咱們須要手動捕獲異常 捕獲異常後能夠處理別的任務 能夠實現單線程併發的效果 但會大量佔用CPU資源 while True: pass 因此,在非阻塞式IO中,用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有。4.多路複用 管理鏈接的一種方式 爲何使用它? 相對於非阻塞IO下降無用的系統調用 怎麼管? 核心函數select 幫你檢測全部的鏈接 找出能夠被處理(能夠讀寫)的鏈接 (默認時阻塞的 阻塞到有任意一個鏈接能夠被處理) 結論: select的優點在於能夠處理多個鏈接,不適用於單個鏈接 一 建立鏈接 和管理鏈接 1.建立服務器socket對象 2.將服務器對象交給select來管理 3.一旦有客戶端發起鏈接 select將不在阻塞 4.select將返回一個可讀的socket對象(第一次只有服務器) 5.服務器的可讀表明有鏈接請求 須要執行accept 返回一個客戶端鏈接conn 因爲是非阻塞 不能當即去recv 6.把客戶端socket對象也交給select來管理 將conn加入兩個被檢測的列表中 7.下一次檢測到可讀的socket 多是服務器 也可能客戶端 因此加上判斷 服務器就accept 客戶端就recv 8.若是檢測到有可寫(能夠send就是系統緩存可用)的socket對象 則說明能夠向客戶端發送數據了 7 和 8 執行順序不是固定的 二 處理數據收發 兩個須要捕獲異常的地方 1.recv 執行第7步 表示能夠讀 爲何異常 只有一種可能客戶端斷開鏈接 還須要加上if not 判斷是否有數據 ;linux下 對方下線不會拋出異常 會收到空消息 2.send 執行第8步 表示能夠寫 爲何異常 只有一種可能客戶端斷開鏈接) 強調: 1. 若是處理的鏈接數不是很高的話,使用select/epoll的web server不必定比 使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。 select/epoll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。 2. 在多路複用模型中,對於每個socket,通常都設置成爲non-blocking,可是,如上圖所示, 整個用戶的process實際上是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。 結論: select的優點在於能夠處理多個鏈接,不適用於單個鏈接5.異步IO 網絡IO+本地IO 都適用 IO包括 網絡IO 本地IO 上面的三種IO模型描述的都是網絡IO,不是本地IO的問題 解決的方案就是: 將同步的IO操做改爲異步的IO操做 在IO期間 能夠執行其餘的任務 最終的解決方案就是協程 使用asyncio模塊 該模快實現異步IO 內部使用協程實現它的流程:用戶進程發起read操做以後,馬上就能夠開始去作其它的事。而另外一方面,從kernel的角度, 當它受到一個asynchronous read以後,首先它會馬上返回,因此不會對用戶進程產生任何block。 而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存, 當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了。socketserver 是什麼? 對服務器端的socket的封裝 封裝了多線程 多進程 IO模型,支撐高併發 高併發 的socket套接字 爲何用? 簡化代碼 使用方法: socketserver (forkingUDP forkingTCP windows沒法使用) 核心類 ThreadingUDPServer ThreadingTCPServer ThreadingTCPServer 實例化時 傳入服務器地址 和 自定義的一個數據處理類 自定義類須要繼承BaseRequestHandler類中需包含handle函數 對象調用serve_foreverTCP服務端import socketserverclass MyHandler(socketserver.BaseRequestHandler): def handler(self): while True: try: data=self.request.recv(1024) if not data:break print(data.decode('utf-8')) self.request.send(data.upper()) except ConnectionResetError: break self.request.close()if __name__ == '__main__': server=socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyHandler) server.serve_forever()UDP服務端import socketserverclass MyHandler(socketserver.BaseRequestHandler): def handle(self): data,server=self.request print(data.decode('utf-8')) server.sendto(data.upper(),self.client_address)if __name__ == '__main__': server=socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyHandler) server.serve_forever()