多路複用IO(瞭解)
阿新 • • 發佈:2018-12-23
多路複用IO(IO multiplexing)
IO multiplexing這個詞可能有點陌生,但是如果我說select/epoll,大概就都能明白了。有些地方也稱這種IO方式為事件驅動IO
(event driven IO)。我們都知道,select/epoll的好處就在於單個process就可以同時處理多個網路連線的IO。它的基本原理就是select/epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有資料到達了,就通知使用者程序。它的流程如圖:
當用戶程序呼叫了select,那麼整個程序會被block,而同時,kernel會“監視”所有select負責的socket, 當任何一個socket中的資料準備好了,select就會返回。這個時候使用者程序再呼叫read操作,將資料從kernel拷貝到使用者程序。 這個圖和blocking IO的圖其實並沒有太大的不同,事實上還更差一些。因為這裡需要使用兩個系統呼叫\(select和recvfrom\), 而blocking IO只調用了一個系統呼叫\(recvfrom\)。但是,用select的優勢在於它可以同時處理多個connection。
強調:
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的優勢在於可以處理多個連線,不適用於單個連線
select網路IO模型示例
#服務端
import socket import select server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("127.0.0.1", 8080)) server.listen(5) server.setblocking(False) # 預設是True為阻塞 print("starting.....") rlist = [server, ] # 專門存收訊息的套接字(server和conn) wlist = [] # 專門存發的套接字 wdata = {} while True: rl, wl, xl = select.select(rlist, wlist, [], 0.5) # []存放異常列表,0.5超時時間(每個0.5s問作業系統) print("rl", rl) print("wl", wl) for sock in rl: if sock == server: conn, addr = sock.accept() rlist.append(conn) else: try: data = sock.recv(1024) # if not data: # 針對linux系統 # sock.close() # rlist.remove() # continue wlist.append(sock) wdata[sock] = data.upper() except Exception: sock.close() rl.remove(sock) for sock in wl: data = wdata[sock] sock.send(data) wlist.remove(sock) wdata.pop(sock)
#客戶端
import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 8080)) while True: msg = input(">>>:").strip() if not msg: continue client.send(msg.encode("utf-8")) data = client.recv(1024) print(data.decode("utf-8"))
select監聽fd變化的過程分析:
使用者程序建立socket物件,拷貝監聽的fd到核心空間,每一個fd會對應一張系統檔案表,核心空間的fd響應到資料後,
就會發送訊號給使用者程序資料已到;
使用者程序再發送系統呼叫,比如(accept)將核心空間的資料copy到使用者空間,同時作為接受資料端核心空間的資料清除,
這樣重新監聽時fd再有新的資料又可以響應到了(傳送端因為基於TCP協議所以需要收到應答後才會清除)。
該模型的優點:
相比其他模型,使用select() 的事件驅動模型只用單執行緒(程序)執行,佔用資源少,不消耗太多 CPU,同時能夠為多客戶端提供服務。
如果試圖建立一個簡單的事件驅動的伺服器程式,這個模型有一定的參考價值。
該模型的缺點:
首先select()介面並不是實現“事件驅動”的最好選擇。因為當需要探測的控制代碼值較大時,select()介面本身需要消耗大量時間去輪詢各個控制代碼。
很多作業系統提供了更為高效的介面,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。
如果需要實現更高效的伺服器程式,類似epoll這樣的介面更被推薦。遺憾的是不同的作業系統特供的epoll介面有很大差異,
所以使用類似於epoll的介面實現具有較好跨平臺能力的伺服器會比較困難。
其次,該模型將事件探測和事件響應夾雜在一起,一旦事件響應的執行體龐大,則對整個模型是災難性的。