模型即解決某個問題的固定套路html
I/O 指的是輸入輸出python
IO的問題: 當咱們要輸入數據或是輸出數據一般須要很長一段時間,固然是對於CPU而言linux
在等待輸入的過程當中,CPU就處於閒置狀態 沒事幹! 形成了資源浪費服務器
注意: IO其實有不少類型,例如,socket網絡IO,內存到內存的copy,等待鍵盤輸入,對比起來socket網絡IO須要等待的時間是最長的,這也是我們重點關注的地方,網絡
學習IO模型要幹什麼? 就是在等待IO操做的過程當中利用CPU,作別的事情多線程
操做系統有兩種狀態:內核態 和 用戶態 , 當操做系統須要控制硬件時,例如接收網卡上的數據,必須先轉換到內核態,接收完數據後,要把數據從操做系統緩衝區,copy到應用程序的緩衝區,從內核態轉爲用戶態;併發
涉及到的步驟app
1.wait_data異步
2.copy_datasocket
recv accept 須要經歷 wait -> copy
send 只須要經歷copy
默認狀況下 你寫出TCP程序就是阻塞IO模型
該模型 提升效率方式,當你執行recv/accept 會進入wait_data的階段,
1.你的進程會主動調用一個block指令,進程進入阻塞狀態,同時讓出CPU的執行權,操做系統就會將CPU分配給其它的任務,從而提升了CPU的利用率
2.當數據到達時,首先會從內核將數據copy到應用程序緩衝區,而且socket將喚醒處於自身的等待隊列中的全部進程
注意:以前使用多線程 多進程 完成的併發 其實都是阻塞IO模型 每一個線程在執行recv時,也會卡住
非阻塞IO模型與阻塞模型相反 ,在調用recv/accept 時都不會阻塞當前線程
使用方法: 將本來阻塞的socket 設置爲非阻塞
該模型在沒有數據到達時,會拋出異常,咱們須要捕獲異常,而後繼續不斷地詢問系統內核直到數據到達爲止
案例:
import socket import time s = socket.socket() s.bind(("127.0.0.1",1688)) # 設置爲非阻塞 模型 s.setblocking(False) #False表示不阻塞 s.listen() # 保存全部的客戶端socket cs = [] # 用於保存須要發送的數據 msgs = [] while True: try: c,addr = s.accept() # 完成三次握手 print("來客了, xxx接客了!") cs.append(c) except BlockingIOError: print("尚未人來/. 好寂寞啊!") # 代碼執行到這裏則說明 沒有鏈接須要處理 # 就能夠處理收發數據的任務 for c in cs[:]: # 專門處理接收數據 try: data = c.recv(1024) if not data: raise ConnectionResetError msgs.append((c,data.upper())) #c.send(data.upper()) # 不能直接發 當內核緩衝區滿了 就會拋出異常 致使數據發不出去了 except BlockingIOError: print("客官 你卻是說句話啊!") pass except ConnectionResetError: c.close() cs.remove(c) # 處理髮送數據 for i in msgs[:]: try: i[0].send(i[1]) msgs.remove(i) except BlockingIOError: pass except ConnectionResetError: #關閉鏈接 i[0].close() # 刪除數據 msgs.remove(i) # 刪除鏈接 cs.remove(i[0])
客戶端
import socket c = socket.socket() c.connect(("127.0.0.1",1688)) while True: msg = input("").strip() if not msg:continue c.send(msg.encode("utf-8")) print(c.recv(1024).decode('utf-8'))
能夠看出,該模型會大量的佔用CPU資源作一些無效的循環, 效率低於阻塞IO
屬於事件驅動模型
多個socket使用同一套處理邏輯
若是將非阻塞IO 比喻是點餐的話,至關於你每次去前臺,照着菜單挨個問個遍
而多路複用,至關於直接問前臺那些菜作好了,前臺會給你返回一個列表,裏面就是已經作好的菜
對比阻塞或非阻塞模型,增長了一個select,來幫咱們檢測socket的狀態,從而避免了咱們本身檢測socket帶來的開銷
select會把已經就緒的socket放入列表中,咱們須要遍歷列表,分別處理讀寫便可
多路複用中select的職責:
案例:
import socket import time import select s = socket.socket() s.bind(("127.0.0.1",1688)) # 設置爲非阻塞 模型 s.setblocking(True) #在多路複用中 阻塞與非阻塞沒有區別 由於select會阻塞直到有數據到達爲止 s.listen() # 待檢測是否可讀的列表 r_list = [s] # 待檢測是否可寫的列表 w_list = [] # 待發送的數據 msgs = {} print("開始檢測了") while True: read_ables, write_ables, _= select.select(r_list,w_list,[]) print("檢測出結果了!") # print(read_ables,"能夠收數據了") # print(write_ables,"能夠發數據了") # 處理可讀 也就是接收數據的 for obj in read_ables: # 拿出全部能夠讀數據的socket #有多是服務器 有多是客戶端 if s == obj: # 服務器 print("來了一個客戶端 要鏈接") client,addr = s.accept() r_list.append(client) # 新的客戶端也交給select檢測了 else:# 若是是客戶端則執行recv 接收數據 print("客戶端發來一個數據") try: data = obj.recv(1024) if not data:raise ConnectionResetError print("有個客戶端說:",data) # 將要發送數據的socket加入到列表中讓select檢測 w_list.append(obj) # 將要發送的數據已經socket對象丟到容器中 if obj in msgs: # 因爲容器是一個列表 因此須要先判斷是否已經存在了列表 msgs[obj].append(data) else: msgs[obj] = [data] except ConnectionResetError: obj.close() r_list.remove(obj) # 處理可寫的 也就是send發送數據 for obj in write_ables: msg_list = msgs.get(obj) if msg_list: # 遍歷發送全部數據 for m in msg_list: try: obj.send(m.upper()) except ConnectionResetError: obj.close() w_list.remove(obj) break # 數據從容器中刪除 msgs.pop(obj) # 將這個socket從w_list中刪除 w_list.remove(obj)
客戶端
import socket c = socket.socket() c.connect(("127.0.0.1",1688)) while True: msg = input("").strip() if not msg:continue c.send(msg.encode("utf-8")) print(c.recv(1024).decode('utf-8'))
多路複用對比非阻塞 ,多路複用能夠極大下降CPU的佔用率
http://www.javashuo.com/article/p-wfbzikdm-dc.html
非阻塞IO不等於異步IO 由於copy的過程是一個同步任務 會卡主當前線程
而異步IO 是發起任務後 就能夠繼續執行其它任務,當數據copy到應用程序緩衝區完成後,纔會給你的線程發送信號 或者執行回調
asyncio python3.4 出現 目前不少重要的第三方模塊都不支持該模塊,因此沒有普遍使用,其內部也是使用了生成器!
簡單的說就是 當某個事情發生後 會給你的線程發送一個信號,你的線程就能夠去處理這個任務
不經常使用,緣由是 socket的信號太多,處理起來很是繁瑣