1、非阻塞套接字
第一部分 基本IO模型
1.普通套接字實作的服務端的缺陷
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLyYzN5QzN1cTNx0SO4YDOygDMzIjNwcDM4EDMy0CO1ATOzMTMvw1NwgTMwIzLchTNwkzMzEzLcd2bsJ2Lc12bj5ycn9Gbi52YugTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
一次隻能服務一個用戶端!
2.普通套接字實作的服務端的瓶頸!!!
accept阻塞!
在沒有新的套接字來之前,不能處理已經建立連接配接的套接字的請求。
recv 阻塞!
在沒有接受到用戶端請求資料之前,
不能與其他用戶端建立連接配接!
3.普通伺服器的IO模型
11 非阻塞套接字與IO多路複用(進階)1、非阻塞套接字2、IO多路複用
第二部分 非阻塞套接字
1.非阻塞套接字與普通套接字的差別
>>> 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] 無法立即完成一個非阻止性套接字操作。
2.使用非阻塞套機字實作阻塞的服務端
# 原來的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
3。非阻塞用戶端套接的注意點
connect操作一定會引發BlockingIOError異常
如果連接配接沒有建立,那麼send操作引發OSError異常
第三部分 非阻塞IO模型
第四部分 使用非阻塞套接字實作并發
1.整體思路
吃滿 CPU !甯可用 whileTrue ,也不要阻塞發呆!
隻要資源沒到,就先做别的事!将代碼順序重排,避開阻塞!
2.實作了什麼?
并發服務多個用戶端!
3.程式設計範式
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
2、IO多路複用
第一部分 不完美的CPU使用率
關鍵一: 任何Python操作都是需要花費CPU資源的 !
關鍵二: 如果資源還沒有到達,那麼
accept、recv以及
send(在connect沒有完成時)
操作都是無效的CPU花費 !
關鍵三: 對應BlockingIOError的異常處理
也是無效的CPU花費 !
第二部分 epoll是真正的答案!
IO多路複用技術
我們把socket交給作業系統去監控
2.epoll是惰性的事件回調
惰性事件回調是由使用者程序自己調用的。
作業系統隻起到通知的作用。
3.為什麼是epoll ?
目前Linux上效率最高的IO多路複用 技術 !
第三部分 IO多路複用選擇器
1.注冊惰性事件回調
>>> 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是對應的回掉函數
2.事件回調
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)
3.程式設計範式
# 使用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)
轉載于:https://www.cnblogs.com/zcmq/p/9275875.html