本系列文章目錄
展開/收起
bio:同步阻塞i/o模式
以下面的**為例:
先是服務端**:
這是客戶端**# coding=utf-8
from threading import thread, currentthread
import socket
# 服務端**
# 建立套接字
server = socket.socket(socket.af_inet, socket.sock_stream)
# 繫結ip和埠
ip = "127.0.0.1"
port = 8000
server.bind((ip, port))
# 監聽套接字
server.listen()
print("服務已開啟")
def contact(client):
print("客戶端 %s 已成功連線" % currentthread().name)
msg = client.recv(1024).decode("utf-8") # 接收客戶端傳送到服務端的訊息,這裡也會收到阻塞
while msg: # 允許接收客戶端傳送多次訊息,如果對方傳送空字元,則認為客戶端斷開連線,此時結束該執行緒
print("客戶端 %s 傳送資訊:%s" % (currentthread().name, msg))
msg = client.recv(1024).decode("utf-8")
print("客戶端 %s 斷開連線" % currentthread().name)
while true:
print("等待接收客戶端連線")
client,addr = server.accept() # 接受連線, 這裡會受到阻塞
# 建立執行緒用於客戶端和服務端通訊
thread = thread(target=contact, args=(client,))
thread.start()
分析如下:# coding=utf-8
from threading import thread, currentthread
import socket
# 客戶端**
# 建立套接字
client = socket.socket(socket.af_inet, socket.sock_stream)
# 繫結ip和埠
ip = "127.0.0.1"
port = 8000
client.connect((ip, port))
while true:
msg = input()
if msg:
client.send(msg.encode("utf-8"))
else: # 如果直接輸入換行則斷開連線
client.close()
break
服務端**使用死迴圈接受多個客戶端的連線,每有乙個連線連進伺服器就開乙個執行緒用於客戶端和服務端進行通訊(其實是簡單的輸出客戶端傳送的訊息而已)。
主線程只負責接收連線,不負責接受訊息;其他執行緒則只負責接受訊息與客戶端通訊。
如果不用多執行緒而是用單執行緒,把接收連線和接受訊息都放在乙個執行緒中
while true:
print("等待接收客戶端連線")
client,addr = server.accept() # 會阻塞
msg = client.recv() # 會阻塞
就會出現這樣的問題:
由於accept 和 recv 都會阻塞,所以當有乙個使用者連線進來,但是它不傳送訊息的話,服務端就會被recv阻塞住。假如此時有其他客戶端連線進來的話,也會被阻塞住,因為服務端的**根本執行不到accept方法,服務端被recv阻塞住,所以其他客戶端根本連不進來。
而開多執行緒可以解決這個問題。
上面執行 socket(),bind(),listen(),accept(),recv() 都會進行系統呼叫。也就是說,執行到這些方法的時候,都會進行使用者態切換到核心態,把cpu讓給核心程式。
像上面這樣呼叫accept(),recv(),connect()方法進行io操作會發生阻塞的情況就是bio。bio的模式需要每建立乙個連線就占用乙個執行緒用來通訊。
下面是網上找到的bio模式的流程圖:
所以bio會出現以下問題:
1.當連線數很多的時候,建立的執行緒數也會很多,而執行緒間的切換會消耗cpu資源,也會損耗切換的時間。資源和時間就都浪費再執行緒的切換上了。cpu跑核心的系統呼叫就已經挺消耗挺大了,還要切換那麼多執行緒,那不就浪費資源嗎。
2.如果客戶端不給服務端傳送訊息,但是又不斷開連線,那麼這個執行緒就相當於什麼事都沒有做,開乙個執行緒卻不做事情,這就是對執行緒資源的一種浪費。
3.斷開連線時,執行緒就直接被銷毀了,執行緒本來就是一種比較珍貴的資源,隨便銷毀也是對資源的浪費。
但是bio的關鍵弊端是因為blocking阻塞,因為recv是阻塞的所以才需要開這麼多執行緒,blocking是根本原因,執行緒太多只是結果。
那怎麼辦?
我們程式設計師無法做出改變,而是需要核心發生改變,因為recv是阻塞的這個事情是核心決定的。
很幸運,之後新版的核心提供了nonblock的socket,這就產生了nio(同步非阻塞i/o模式)
nio模型:同步非阻塞i/o模式
這種模式下,accept和recv,接收連線和接收訊息都是非阻塞的。這麼一來,我們就無需開多個執行緒用於通訊,只需在乙個執行緒中輪詢多個客戶端檢視他們有沒有發訊息即可。
**實現如下:
nio模型的優點是非阻塞所以避免了多執行緒,減少了執行緒間切換的開銷# coding=utf-8
from threading import thread, currentthread
import socket
from time import sleep
# 服務端**
# 建立套接字
server = socket.socket()
# 繫結ip和埠
ip = "127.0.0.1"
port = 8000
server.bind((ip, port))
server.listen(3) # 只允許最多3個客戶端連線
server.setblocking(false) # 非阻塞socket
print("server服務已開啟")
clients = dict() # 用於儲存所有建立了連線的客戶端
no = 1 # 客戶端編號
while true:
try:
client,addr = server.accept() # 接收連線,非阻塞, (核心)會馬上返回。如果沒有接收到連線則丟擲乙個異常
print("接收到客戶端")
clients[no] = client
no += 1
client.setblocking(false) # 設定客戶端socket為非阻塞,這樣後面呼叫recv就是非阻塞的了
except blockingioerror:
# print("未接收到客戶端")
sleep(0.1)
for client_no in list(clients.keys()): # 遍歷所有連線的客戶端,接收他們傳送的訊息,這裡要用list函式轉一下clients.keys(),否則在刪除字典中的客戶端時再迴圈會報錯說字典遇到改變
each_client = clients[client_no]
try:
msg = each_client.recv(1024) # 非阻塞,(核心)會馬上返回。如果沒有接收到訊息則丟擲乙個異常
if not msg: # 如果傳送空訊息表示連線已斷開
each_client.close()
del clients[client_no] # 從列表中移除該客戶端
print("客戶端 %s 斷開連線" % str(client_no))
else:
print("客戶端 %s 傳送訊息:%s" % (str(client_no), msg.decode('utf-8')))
sleep(0.1)
except blockingioerror: # 客戶端未傳送訊息
pass
缺點:要不斷迴圈所有客戶端檢視客戶端是否有傳送訊息。而且每一次迴圈都要呼叫accept和recv,也就是說,每一次迴圈都要進行系統呼叫,而系統呼叫本身就是乙個比較消耗cpu的操作,涉及到使用者態核心態的切換以及上下文切換。
也就是說通過迴圈的方式會很消耗cpu。
而且,不一定有客戶端傳送訊息,假如有1萬個客戶端,只有乙個客戶端傳送了訊息,那麼1萬次輪詢只有1次是有效的,因此用輪詢的方式是很浪費,很沒有效率的。
下面是網上找到的nio模式的流程圖
有沒有什麼辦法,可以不通過輪詢,而是讓核心通知使用者程序哪些客戶端已經傳送了訊息可以去直接recv讀取這些客戶端的訊息呢。
張柏沛it技術部落格 >
從io模型到協程(二) bio模型和nio模型
協程和IO模型
協程 1.什麼是協程 單執行緒實現併發 在應用程式裡控制多個任務的切換 儲存狀態 優點 應用程式級別速度要遠遠高於作業系統的切換 缺點 多個任務一旦有乙個阻塞沒有切,整個執行緒都阻塞在原地 該執行緒內的其他的任務都不能執行了 一旦引入協程,就需要檢測單執行緒下所有的io行為,實現遇到io就切換,少乙...
協程 IO模型
一 協程 1.定義 單執行緒實現併發,可以再應用程式當中控制多個任務的切換 儲存狀態。優點 在應用程式級別的速度要遠遠高於作業系統的切換 缺點 多個任務一旦有乙個任務阻塞住了,沒有及時切換,整個執行緒都將阻塞在原地,該執行緒內的其他任務都不能繼續執行了。所以,在引入協程之後,就需要檢測單執行緒下所有...
詳解協程,事件驅動模型,I O模型
什麼是事件驅動模型 協程介紹 協程是什麼 協程是微執行緒,他是一種使用者態的輕量級執行緒,它的好處是沒有執行緒切換的開銷,我們開多執行緒,執行緒的上下文切換是耗費cpu的開銷的,但是多協程裡邊,只是乙個控制流調到了另外乙個控制流,它還是單執行緒,所以協程很適合高併發,乙個cpu可以支援上萬的協程,因...