說說基於網絡的五種IO模型
阿新 • • 發佈:2018-10-28
處理 所有 cpe 客戶 分享 for imp stream targe
# django不是一個異步框架 # tornado是異步的web框架 # 處理每秒大量的請求 # 個人理解的IO:就是應用層與內核驅動層的交互,這個過程無論從應用層到內核中,還是驅動層等待硬件層的數據,都是需要時間的,這個過程是IO操作過程 # 五種IO Model # blocking IO 阻塞IO # nonblocking 非阻塞IO # IO multiplexing IO多路復用 # signal driven IO 信號驅動IO # asynchronous IO 異步IO # 在python中沒有提供異步IO的機制,沒有操作系統將數據直接給應用層的獲取到數據的接口. 但是由很多python的異步框架# 這種異步IO機制其實是很好的
# IO發生時涉及的對象和步驟,對於一個network IO,我們以read舉例,他會涉及到兩個系統對象,一個是調用這個IO的process(or thread),另一個就是系統內核。當一個read操作時,該操作會經歷兩個階段 # 1.等待數據準備 # 2.將數據從內核拷貝到應用層
# 同步 提交一個任務之後要等待這個任務執行完畢才能繼續執行其他的 # 異步 只管提交任務,不用等待該任務執行完畢就可以繼續做其他事情 # 阻塞 運行狀態 -> 阻塞狀態 -> 就緒狀態 -> 運行狀態、一進程或線程阻塞則會進入阻塞狀態休眠# 非阻塞
1.非阻塞IO
# 非阻塞IO # import socket # sk = socket.socket() # sk.setblocking(False) # 設置非阻塞 # sk.bind((‘127.0.0.1‘, 8080)) # sk.listen() # # try: # conn, addr = sk.accept() # 因為將套接字設置為了非阻塞,所以這裏會報錯,因為accept不允許為非阻塞性的,所以下面捕捉異常,有異常則直接pass # print(‘有客戶端連接上來 ‘) # except BlockingIOError: # pass
2.阻塞IO
3.IO多路復用
# 在windows、linux上,有一個select專門提供IO多路復用的。 # poll機制 # linux上有 # epoll機制 # linux上有 # poll機制和epoll機制使用方法和select一樣 # poll可以監聽的對象比select可以監聽的多。如果select能監聽500,則poll能監聽1000個類似這樣 # poll和select都是操作系統去輪詢機制的監聽被監聽的項,看是否有讀操作等,隨著監聽列表增多,會導致效率變差 # epoll機制 # 給每一個被監聽的對象都綁定了一個回調函數,當被監聽的對象有監聽事件後,會觸發此監聽對象綁定的回調函數這種機制比select和poll的輪詢效率要高,高並發非常有用 # import selectors 這個模塊會幫助你選擇當前操作系統上最優的IO多路復用
3.1 IO多路復用中的select
服務端
import select # 內置的select模塊,用於IO多路復用 import socket # select.select(rlist, wlist, xlist, timeout=None) # 參數是3個列表,一個監聽超時時間 # rlist參數,表示監聽讀,直到監聽到讀或超時返回 # wlist參數,表示監聽寫,直到監聽到寫或超時返回 # xlist參數,表示監聽條件 # timeout參數,監聽超時時間 # 返回值有三個 sk = socket.socket() sk.bind((‘127.0.0.1‘, 8080)) sk.setblocking(False) # 設置為非阻塞 sk.listen() read_lst = [sk] # 創建一個列表,想要監聽誰,則將哪個對象放進來,這裏開始先監聽socket對象,當有人向這個socket發起連接的時候,select則會監聽到返回一個socket while 1: # 調用select後操作系統會幫你監聽三個監聽列表,這裏監聽的是監聽讀列表 r_lst, w_lst, x_lst = select.select(read_lst, [], []) # 當監聽到後,返回一個元組,元組中有三個元素,分別是rlist,wlist,xlist。這裏rlist中最開始監聽sk,因此當有客戶端連接上來後,會監聽有要被讀的事件,這裏會返回得到一個r_lst,r_lst中有一個sk對象 #print(r_lst) for i in r_lst: if i is sk: # 判斷監聽到的對象是否是sk對象 conn, addr = i.accept() # 此時直接sk.accpet()就會得到客戶端連接和地址 read_lst.append(conn) # 將客戶端連接符加入到監聽列表中 else: # 如果監聽到的不是sk對象, 這裏的第二可能是監聽到了客戶端連接符有要被讀的事件 msg = i.recv(1024) if msg == b‘‘: # 當客戶端連接關閉時,會接收到空的數據, i.close() # 因為客戶端連接主動關閉,這裏也要關閉下這個客戶端連接 read_lst.remove(i) # 同時在監聽列表中去除這個客戶端連接符,不再去監聽它 continue print(msg)
3.2 linux上更好的IO多路復用epoll、selectors選擇當前系統最優的IO多路復用機制
服務端
# linux上的selectors IO多路復用機制,IO多路復用機制,默認選擇系統最優,linux上肯定選擇epoll import selectors from socket import * def read(conn, mask): ‘‘‘ 將來要綁定的回調函數 :param conn: :param mask: :return: ‘‘‘ try: data = conn.recv(1024) if not data: # 如果監聽客戶端的數據是空數據,則表示客戶端連接關閉了 print(‘closeing‘, conn) sel.unregister(conn) # 在監聽列表中去除這個客戶端連接的監聽 conn.close() # 同時服務端也關閉這個客戶端連接描述符 return conn.send(data.upper() + b‘SB‘) except Exception: # 該客戶端連接描述符監聽到異常事件,則直接關閉客戶端連接 print(‘closing‘, conn) sel.unregister(conn) # 在監聽列表中去除這個客戶端連接的監聽 conn.close() # 同時服務端也關閉這個客戶端連接描述符 def accept(server_fileobj, mask): ‘‘‘ :param server_fileobj: 接收到的socket :param mask: :return: ‘‘‘ conn, addr = server_fileobj.accpet() # 得到客戶端連接描述符 sel.register(conn, selectors.EVENT_READ, read) # 將conn客戶端連接符註冊到監聽列表中,監聽的是其讀時間,綁定的回調函數是read if __name__ == ‘__main‘: sk = socket() sk.setblocking(SOL_SOCKET, SO_REUSEPORT, 1) sk.bind((‘127.0.0.1‘, 8080)) sk.listen(5) sk.setblocking(False) sel = selectors.DefaultSelector() # 獲取到當前操作系統最優的IO多路復用機制 sel.register(sk, selectors.EVENT_READ, accept) # 將socket對象註冊到監聽列表中,監聽其讀事件,當有讀事件時綁定的回調函數accpet會被執行 # server_fileobj = socket(AF_INET, SOCK_STREAM) # server_fileobj.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1) # server_fileobj.bind((‘127.0.0.1‘, 8080)) # server_fileobj.listen(5) # server_fileobj.setblocking(False) # sel.register(server_fileobj, selectors.EVENT_READ, accept) while True: events = sel.select() # 檢測到所有的fileobj(監聽列表中的所有對象,都是文件描述符),是否有完成wait data階段。當監聽到有事件時返回 for sel_obj, mask in events: callback = sel_obj.data # 第一次是callback = accpet 通過sel_obj.data就能拿到剛剛這個被監聽對象的回調函數 callback(sel_obj.fileobj, mask) # 第一次是ccpet(server_fileobj, 1) 直接調用回調函數
客戶端
import socket from threading import Thread def func(): sk = socket.socket() sk.connect((‘127.0.0.1‘, 8080)) sk.send(b‘hello‘) sk.close() if __name__ == ‘__main__‘: for i in range(20): Thread(target=func).start()
4.信號驅動IO
5.異步IO
6.五種IO模型的比較,個人覺得肯定還是異步IO好
說說基於網絡的五種IO模型