一次只能服務一個客戶端!python
accept阻塞!shell
在沒有新的套接字來以前,不能處理已經創建鏈接的套接字的請求。編程
recv 阻塞!服務器
在沒有接受到客戶端請求數據以前,併發
不能與其餘客戶端創建鏈接!app
>>> import socket >>> server = socket.socket() # 講socket設置成非阻塞 >>> server.setblocking(False) # 注意!這必需要在其餘操做以前! >>> server.bind(('',8080)) >>> server.listen(5) >>> server.accept() # 沒有鏈接就引起BlockingIOError Traceback (most recent call last): File "<pyshell#5>", line 1, in <module> server.accept() File "E:\python\lib\socket.py", line 205, in accept fd, addr = self._accept() BlockingIOError: [WinError 10035] 沒法當即完成一個非阻止性套接字操做。 # 使用一個客戶端(普通的就行,不須要非阻塞)鏈接過來 >>> conn,addr = server.accept() # 有鏈接則正確返回 >>> conn.recv(1024) # 沒有鏈接數據就引起BlockingIOError Traceback (most recent call last): File "<pyshell#9>", line 1, in <module> conn.recv(1024) BlockingIOError: [WinError 10035] 沒法當即完成一個非阻止性套接字操做。
# 原來的recv while True: try: recv_data = conn.recv(1024) break except BlockingIOError: pass # 原來的accept while True: try: conn,addr = server.accept() break except:BlockingIOError: pass
connect操做必定會引起BlockingIOError異常socket
若是鏈接沒有創建,那麼send操做引起OSError異常函數
吃滿 CPU !寧肯用 whileTrue ,也不要阻塞發呆!spa
只要資源沒到,就先作別的事!將代碼順序重排,避開阻塞!操作系統
併發服務多個客戶端!
import socket server = socket.socket() # 生成套接字 server.setblocking(False) # 非阻塞 server.bind(('',7788)) server.listen(1000) # 咱們如今生成的非阻塞套接字,非阻塞套接字在執行accept跟recv的時候不會阻塞,可是會報錯, # 因此咱們寫非阻塞的併發服務器須要用到異常處理 all_conn = [] # 用來保存咱們全部的已經生成的套接字(這個客戶端還在鏈接着) while True: # 處理鏈接,生成對等鏈接套接字 try: conn,addr = server.accept() conn.setblocking(False) # conn是新生成的,須要給它設置一下非阻塞 all_conn.append(conn) # 這一行代碼的前提是,上面一行代碼正常返回 except BlockingIOError: pass for conn in all_conn: try: # 只負責接受數據 recv_data = conn.recv(1024) if recv_data: res = recv_data.decode() print(res) conn.send(recv_data) else: conn.close() all_conn.remove(conn) # 客戶端關閉鏈接,就把它移除 except BlockingIOError: pass
關鍵一: 任何Python操做都是須要花費CPU資源的 !
關鍵二: 若是資源尚未到達,那麼
accept、recv以及
send(在connect沒有完成時)
操做都是無效的CPU花費 !
關鍵三: 對應BlockingIOError的異常處理
也是無效的CPU花費 !
咱們把socket交給操做系統去監控
惰性事件回調是由用戶進程本身調用的。
操做系統只起到通知的做用。
目前Linux上效率最高的IO多路複用 技術 !
>>> import socket >>> import selectors >>> server = socket.socket() >>> server.bind(('',9000)) >>> server.listen(1000) >>> selector = selectors.EpollSelector() # 實例化一個 epoll 選擇器 >>> def create_conn(server): ... conn,addr = server.accept() ... return conn ... # 套接字、事件、回調函數 >>> selector.register(server,selectors.EVENT_READ,create_conn) SelectorKey(fileobj=<socket.socket fd=3, # 生成一個打包對象 family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 9000)>, # fileobj是對應套接字 fd=3, events=1, # 事件(1 表示EVENT_READ) data=<function create_conn at 0xb70b7b24>) # data是對應的回掉函數
events = selector.select() # 查詢,返回全部已經準備好的資源打包對象 print(enents) # 是一個 ‘二元組’ 的列表 [(SelectorKey(fileobj=<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 8888)>, fd=4, events=1, data=<function accept at 0xb71f292c>), 1)] # 咱們只須要關心,每一個元祖的第一項(即打包對象,其中包含了對應的套接字與回掉函數) # 接下來並不須要關心是什麼套接字,什麼事件,只須要調用對應的對調函數便可 callbeck = events[0][0].data sock = events[0][0].fileojb callbeck(sock)
# 使用EpollSelector,實現一個併發的服務器 import socket import selectors # IO多路複用選擇器的模塊,接口,調用epoll epoll_selector = selectors.EpollSelector() # 建立一個用來和epoll通訊的選擇器 server = socket.socket() server.bind(('',8888)) server.listen(1000) def read(conn): recv_data = conn.recv(1024) if recv_data: print(recv_data.decode()) conn.send(recv_data) else: epoll_selector.unregister(conn) # 如今數據已經傳輸完了,那我如今就不用再去監控它了,因此# 關閉監控 conn.close() # 關閉鏈接 def accept(server): conn, addr = server.accept() # 生成一個對等鏈接套接字 # 要準備接受數據 epoll_selector.register(conn, selectors.EVENT_READ, read) epoll_selector.register(server,selectors.EVENT_READ, accept) # 註冊事件在能夠讀的時候的回調函數 # 事件循環(主動去問epoll,哪些socket能夠回調了,若是有了,那我就回調) while True: events = epoll_selector.select() # 查詢全部的已經準備好的事件,返回一個列表(二元組列表) # a, b = events for key, mask in events: # 第一項是咱們須要用的 callback = key.data # 從key裏面把回掉函數拿出來 sock = key.fileobj # 從key裏面把咱們註冊的那個socket拿出來 callback(sock)