非阻塞:指在不能馬上獲得結果以前,該函數不會阻塞當前線程,而會馬上返回。epoll工做在非阻塞模式時,纔會發揮做用。編程
咱們瞭解了socket以後已經知道,普通套接字實現的服務端的缺陷:一次只能服務一個客戶端!服務器
而且,爲了使一個客戶端可以不斷收發消息,咱們還要使用while循環來輪詢,這極大地下降了咱們的效率多線程
accept阻塞!併發
在沒有新的套接字來以前,不能處理已經創建鏈接的套接字的請求app
recv 阻塞!socket
在沒有接受到客戶端請求數據以前,不能與其餘客戶端創建鏈接函數
能夠用非阻塞接口來嘗試解決這個問題!spa
阻塞IO模型操作系統
阻塞IO(blocking IO)的特色:就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了。線程
什麼是阻塞呢?想象這種情形:你要去車站接朋友,到了車站以後發現車還沒到站,你如今有兩種選擇:
很明顯,大多數人都會選擇第二種方案。
而在計算機世界,這兩種情形就對應阻塞和非阻塞忙輪詢。
非阻塞IO模型
非阻塞套接字和阻塞套接字的區別:
把套接字設置爲非阻塞以後,若是沒有獲得想要的數據,就會拋出一個BlockingIOError的異常。咱們能夠經過捕獲處理這個異常,讓程序正常完成
非阻塞式IO中,用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有
非阻塞如何利用
編程範式:
服務端
import socket CONN_ADDR = ('127.0.0.1', 9999) conn_list = [] # 鏈接列表 server = socket.socket() # 開啓socket server.setblocking(False) # 設置爲非阻塞 server.bind(CONN_ADDR) # 綁定IP和端口到套接字 server.listen(5) # 監聽,5表示最大掛起數 print('start listen') while True: try: conn,addr = server.accept() #等待客戶端鏈接,沒有就拋出BlockingIOError conn.setblocking(False) print('{}已鏈接'.format(addr)) conn_list.append(conn) except BlockingIOError: pass conn_list = [x for x in conn_list] for conn_socket in conn_list:#對已鏈接的套接字進行輪詢 try: data = conn_socket.recv(1024) #若有客戶端發送消息,則打印並返回 except BlockingIOError: pass else: #else在不報錯的時候才執行 if data: #判斷客戶端發過來的是否是空 print(data.decode()) conn_socket.send(data) else: #若爲空,表示客戶端已斷開 conn_socket.close() conn_list.remove(conn_socket) print('客戶端數目:{}'.format(len(conn_list)))
客戶端
import socket client = socket.socket() client.connect(('127.0.0.1',9999)) while True: data = input('>>>>>') if data == 'q': #按q退出 break client.send(data.encode()) response = client.recv(1024) print(response.decode())
非阻塞IO模型優勢:實現了同時服務多個客戶端,可以在等待任務完成的時間裏幹其餘活了(包括提交其餘任務,也就是 「後臺」 能夠有多個任務在「」同時「」執行)。
可是非阻塞IO模型毫不被推薦
非阻塞IO模型缺點:
如何解決:多路複用IO
什麼是IO多路複用技術呢,簡單來講,就是咱們把套接字交給操做系統去監控。
使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操做,感受效率更差。
可是,使用select之後最大的優點是用戶能夠在一個線程內同時處理多個socket的IO請求。用戶能夠註冊多個socket,而後不斷地調用select讀取被激活的socket,
便可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須經過多線程的方式才能達到這個目的。
epoll是目前Linux上效率最高的IO多路複用技術。
epoll是惰性的事件回調,惰性事件回調是由用戶進程本身調用的,操做系統只起到通知的做用。
epoll實現併發服務器,處理多個客戶端
import socket import selectors # 註冊一個epllo事件參數 # 1. 須要操做系統監控的套接字 # 2.事件(可讀仍是可寫) # 3.回調函數 def recv_data(conn): data = conn.recv(1024) if data: print('接收的數據是:%s' % data.decode()) conn.send(data) else: print('斷開鏈接',conn) e_poll.unregister(conn) conn.close() def accept_conn(p_server): conn, addr = p_server.accept() print('Connected by', addr) # 也要註冊一個事件 e_poll.register(conn,selectors.EVENT_READ,recv_data) CONN_ADDR = ('127.0.0.1', 9999) server = socket.socket() server.bind(CONN_ADDR) server.listen(6) # 生成一個epllo選擇器實例 I/O多路複用,監控多個socket鏈接 e_poll = selectors.DefaultSelector() # Linux是epoll,Windows是select e_poll.register(server, selectors.EVENT_READ, accept_conn) # 事件循環 while True: # 事件循環不斷地調用select獲取發生變化的socket events = e_poll.select() for key, mask in events: call_back = key.data #key.data就是回調函數 call_back(key.fileobj) #key.fileobj是套接字