Linux下,能夠經過設置socket使其變爲non-blocking。當對一個non-blocking socket執行讀操做時,流程是這個樣子:python
從圖中能夠看出,當用戶進程發出read操做時,若是kernel中的數據尚未準備好,那麼它並不會block用戶進程,而是馬上返回一個error。從用戶進程角度講 ,它發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果。用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而用戶就能夠在本次到下次再發起read詢問的時間間隔內作其餘事情,或者直接再次發送read操做。一旦kernel中的數據準備好了,而且又再次收到了用戶進程的system call,那麼它立刻就將數據拷貝到了用戶內存(這一階段仍然是阻塞的),而後返回。app
也就是說非阻塞的recvform系統調用調用以後,進程並無被阻塞,內核立刻返回給進程,若是數據還沒準備好, 此時會返回一個error。進程在返回以後,能夠乾點別的事情,而後再發起recvform系統調用。重複上面的過程, 循環往復的進行recvform系統調用。這個過程一般被稱之爲輪詢。輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程, 進行數據處理。須要注意,拷貝數據整個過程,進程仍然是屬於阻塞的狀態。
因此,在非阻塞式IO中,用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有。socket
#!/usr/bin/python3 # -*- coding: utf-8 -*- # @Time : 2018/6/19 9:33 # @File : server1.py from socket import * server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8080)) server.listen(5) server.setblocking(False) # 使得下面全部IO都是非阻塞 print('starting...........') rlist = [] wlist = [] while True: try: conn, addr = server.accept() # 創建鏈接 rlist.append(conn) print(rlist) except BlockingIOError: # 收消息 del_rlist = [] for conn in rlist: try: data = conn.recv(1024) if not data: del_rlist.append(conn) continue # con.send(data.upper()) # 這種方式在數據量大的時候也會有問題 wlist.append((conn, data.upper())) except BlockingIOError: continue except Exception: conn.close() del_rlist.append(conn) # 發消息 del_wlist = [] for item in wlist: try: conn = item[0] data = item[1] conn.send[data] del_wlist.append(item) except BlockingIOError: pass for item in del_wlist: wlist.remove(item) for del_con in del_rlist: rlist.remove(del_con) server.close()
# 缺點,一、沒法及時響應客戶端二、cpu無用功一直運行(死循環)ide
#!/usr/bin/python3 # -*- coding: utf-8 -*- # @Time : 2018/6/19 9:33 # @File : client1.py # 右鍵運行一次就是一個進程 from socket import * client = socket(AF_INET, 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')) client.close()
咱們不可否則其優勢:可以在等待任務完成的時間裏幹其餘活了(包括提交其餘任務,也就是 「後臺」 能夠有多個任務在「」同時「」執行)。spa
可是也難掩其缺點:操作系統
1. 循環調用recv()將大幅度推高CPU佔用率;這也是咱們在代碼中留一句time.sleep(2)的緣由,不然在低配主機下極容易出現卡機狀況 2. 任務完成的響應延遲增大了,由於每過一段時間纔去輪詢一次read操做,而任務可能在兩次輪詢之間的任意時間完成。 這會致使總體數據吞吐量的下降。
此外,在這個方案中recv()更多的是起到檢測「操做是否完成」的做用,實際操做系統提供了更爲高效的檢測「操做是否完成「做用的接口,例如select()多路複用模式,能夠一次檢測多個鏈接是否活躍。3d