python-day38--IO模型
一、 IO模型介紹
對於一個網絡通信,IO涉及到兩個階段
1.操作系統等數據來
2.進程或線程等操作系統拷貝數據
記住這兩點很重要,因為這些IO模型的區別就是在兩個階段上各有不同的情況。
二、阻塞IO(blocking IO)
例子:
1 from socket import * 2 s=socket(AF_INET,SOCK_STREAM) 3 s.bind((‘127.0.0.1‘,8080)) 4 s.listen(5) 5 print(‘starting..‘) 6 while True: 7 conn,addr=s.accept() # accept就是IOsocket 通信服務端8 print(addr) 9 while True: 10 try: 11 data=conn.recv(1024) #recv 也是IO 12 if not data : break 13 conn.send(data.upper()) 14 except Exception: 15 break 16 conn.close() 17 s.close()
1 from socket import * 2socket 通信客戶端c=socket(AF_INET,SOCK_STREAM) 3 c.connect((‘127.0.0.1‘,8080)) 4 5 while True: 6 cmd=input(‘ss‘).strip() 7 if not cmd:continue 8 c.send(cmd.encode(‘utf-8‘)) 9 data=c.recv(1024) 10 print(data.decode(‘utf-8‘))
所以,blocking IO的特點就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了。
一個簡單的解決方案:
#在服務器端使用多線程(或多進程)。多線程(或多進程)的目的是讓每個連接都擁有獨立的線程(或進程),這樣任何一個連接的阻塞都不會影響其他的連接。
該方案的問題是:
#開啟多進程或都線程的方式,在遇到要同時響應成百上千路的連接請求,則無論多線程還是多進程都會嚴重占據系統資源,降低系統對外界響應效率,而且線程與進程本身還是沒有解決IO問題,只是換了一種方式
改進方案:
#很多程序員可能會考慮使用“線程池”。“線程池”旨在減少創建和銷毀線程的頻率,其維持一定合理數量的線程,並讓空閑的線程重新承擔新的執行任務。這種技術都可以很好的降低系統開銷,都被廣泛應用很多大型系統,如websphere、tomcat和各種數據庫等。 這種方式確實是好了些,但是還沒有解決IO 問題
對應上例中的所面臨的可能同時出現的上千甚至上萬次的客戶端請求,“線程池”或許可以緩解部分壓力,但是不能解決所有問題。總之,多線程模型可以方便高效的解決小規模的服務請求,但面對大規模的服務請求,多線程模型也會遇到瓶頸,可以用非阻塞接口來嘗試解決這個問題。
三 非阻塞IO(non-blocking IO)
1 from socket import * 2 import time 3 s=socket(AF_INET,SOCK_STREAM) 4 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 5 s.bind((‘127.0.0.1‘,8085)) 6 s.listen(5) 7 s.setblocking(False) #套接字裏面的所有阻塞操作都會變成非阻塞 8 print(‘starting...‘) 9 l=[] 10 d_l=[] 11 while True: 12 try: 13 print(l) 14 conn,addr=s.accept() 15 l.append(conn) 16 except BlockingIOError: #捕捉 accept收不到數據的時候拋出的異常 17 for conn in l: 18 try: 19 data=conn.recv(1024) 20 conn.send(data.upper()) 21 except BlockingIOError: #捕捉 recv收不到數據的時候拋出的異常 22 pass 23 except ConnectionResetError: #捕捉 客戶端突然斷開鏈接的時候拋出的異常 24 d_l.append(conn) 25 for j in d_l: 26 j.close() 27 l.remove(j) 28 d_l=[]服務端
1 from socket import * 2 c=socket(AF_INET,SOCK_STREAM) 3 c.connect((‘127.0.0.1‘,8085)) 4 5 while True: 6 cmd=input(‘ss‘).strip() 7 if not cmd:continue 8 c.send(cmd.encode(‘utf-8‘)) 9 data=c.recv(1024) 10 print(data.decode(‘utf-8‘))客戶端
但是非阻塞IO模型絕不被推薦。
缺點:
#1. 循環調用recv()將大幅度推高CPU占用率;容易出現卡機情況 #2. 任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次accept操作,而任務可能在兩次輪詢之間的任意時間完成。這會導致整體數據吞吐量的降低。View Code
四 多路復用IO(IO multiplexing) select 模塊
1 from socket import * 2 import time 3 import select #自動監聽多個套接字 誰好了就可以來取了 4 server=socket(AF_INET,SOCK_STREAM) 5 server.bind((‘127.0.0.1‘,8085)) 6 server.listen(5) 7 print(‘starting...‘) 8 reads_l=[server,] 9 while True: 10 r_l,_,_=select.select(reads_l,[],[]) #select.select返回的是 讀列表裏已經準備好的套接字 11 print(r_l) 12 for obj in r_l: 13 if obj == server: 14 conn,addr=obj.accept() #obj=server 15 reads_l.append(conn) 16 else: 17 try: 18 data=obj.recv(1024) #obj=conn 19 obj.send(data.upper()) 20 except ConnectionResetError: # 捕捉 客戶端突然斷開鏈接 造成的異常 21 pass服務端
1 from socket import * 2 c=socket(AF_INET,SOCK_STREAM) 3 c.connect((‘127.0.0.1‘,8085)) 4 5 while True: 6 cmd=input(‘ss‘).strip() 7 if not cmd:continue 8 c.send(cmd.encode(‘utf-8‘)) 9 data=c.recv(1024) 10 print(data.decode(‘utf-8‘))客戶端
select 從流程上分析:效率不如 阻塞IO 的效率高,但是阻塞不能實現並發,
如果select只檢測一個套接字,效率肯定不如阻塞IO,但是它可以同時監測多個套接字,而阻塞IO 遠遠不行。
select 和 非阻塞IO 比較: 非阻塞IO 是自己捕捉信號,自己處理多個套接字的,並且非常占用CPU,而select 是自動捕捉的
select監聽的套接字個數不是無限多的
五、selectors模塊
select,poll,epoll
epoll模型可以解決套接字個數非常多的情況(因為它的內部檢測哪個套接字好了的機制和select不同(select是遍歷每個套接字,看有沒有好了的,而epoll是 如果哪個套接字好了,就自動跑出來)),但是windows不支持epoll模型
這三種IO多路復用模型在不同的平臺有著不同的支持,而epoll在windows下就不支持,好在我們有selectors模塊,幫我們默認選擇當前平臺下最合適的
from socket import * import selectors #導入selectors後,會根據你的系統 自動選擇當前系統下最合適的IO模型
python-day38--IO模型