python 粘包現象
阿新 • • 發佈:2018-10-18
write () 用戶輸入 windows系統 RoCE 全部 讀取數據 int lose
一. 粘包現象
1. 粘包現象的由來
(1)TCP屬於長連接,當服務端與一個客戶端進行了連接以後,其他客戶端需要(排隊)等待.若服務端想要連接另一個客戶端,必須首先斷開與第一個客戶端的連接.
(2)緩沖區:
a. 每個socket(套接字)被創建後,都會分配兩個緩沖區: 輸入緩沖區和輸出緩沖區.
b. write()/send()並不立即向網絡中傳輸數據,而是先將數據寫入緩沖區中,再由TCP協議將數據從緩沖區發送到目標機器. 一旦將數據寫入到緩沖區,函數就已經完成任務可以成功返回了,而不用去考慮數據何時被發送到網絡,也不用去考慮數據是否已經到達目標機器,因為這些後續操作都是TCP協議負責的事情.
c. TCP協議獨立於 write()/send() 函數,數據有可能剛被寫入緩沖區就發送到網絡,也可能在緩沖區中不斷積壓,多次寫入的數據被一次性發送到網絡,這取決於當時的網絡情況,當前線程是否空閑等諸多因素,不由程序員控制.
d. read()/recv() 函數也是如此,也從輸入緩沖區中讀取數據,而不是直接從網絡中讀取.
e. 這些I/O緩沖區特性可整理如下:
1). I/O緩沖區在每個TCP套接字中單獨存在
2). I/O緩沖區在創建套接字時自動生成
3). 即使關閉套接字也會繼續傳送輸出緩沖區中遺留的數據
4). 關閉套接字將丟失輸入緩沖區中的數據
5). 輸入/輸出緩沖區的默認大小一般是8K(了解:可以通過getsockopt()函數獲取)
f. 如果一次性最多輸入/輸出的數據量超出了緩沖區的大小,系統就會報錯.例如,在UDP協議下,一個數據包的大小超過了一次recv()方法能接受數據量大小,就會報錯.
2. subprocess模塊 的簡單用法
import subprocess
cmd = imput("請輸入指令>>>")
result = subprocess.Popen(
cmd, # 字符串指令: "dir","ipconfig",等等
shell=True, # 使用shell,就相當於使用cmd窗口
stderr=subprocess.PIPE, # 標準錯誤輸出,凡是輸入錯誤指令,錯誤指令輸出的報錯信息就會被它拿到
stdout=subprocess.PIPE, # 標準輸出,正確指令的輸出結果會被它拿到
)
print(result.stdout.read().decode("gbk"))
print(result.stderr.read().decode("gbk"))
註意: 如果是windows,那麽result.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼且只能從管道裏讀一次結果,PIPE稱為管道.
3. 粘包現象模擬(tcp協議下)
(1)發送方連續發送較小的數據,並且每次發送之間的時間間隔很短,此時,兩個消息在輸出緩沖區黏在一起了.原因是TCP為了傳輸效率,做了一個優化算法(Nagle),減少連續的小包發送(因為每個消息被包裹以後,都會有兩個過程:組包和拆包,這兩個過程是極其消耗時間的,優化算法Magle的目的就是為了減少傳輸時間)
服務端(接收方):
import socket
server = socket.socket()
ip_port = ("192.168.15.28", 8001)
server.bind(ip_port)
server.listen()
conn, addr = server.accept()
# 連續接收兩次消息
from_client_msg1 = conn.recv(1024).decode("utf-8")
print("第一次接收到的消息>>>", from_client_msg1)
from_client_msg2 = conn.recv(1024).decode("utf-8")
print("第二次接收到的消息>>>", from_client_msg2)
conn.close()
server.close()
客戶端(發送方):
import socket
client = socket.socket()
server_ip_port = ("192.168.15.28", 8001)
client.connect(server_ip_port)
# 連續發送兩次消息
client.send(‘Hello‘.encode(‘utf-8‘))
client.send(‘World‘.encode(‘utf-8‘))
client.close()
先運行服務端,再運行客戶端,最終在服務端看到的執行結果是:
第一次接收到的消息>>> HelloWorld
第二次接收到的消息>>>
客戶端連續發送了兩次消息,第一次發送"Hello",第二次發送"World".雙方執行程序後卻發現,服務端本應接收到兩個消息,然而實際情況是:服務端在第一次就收到了所有消息,並且兩個消息緊緊靠在一起,出現了"粘包現象"!
(2)發送方第一次發送的數據比接收方設置的"一次接收消息的大小"要大,於是會出現一次接收不完的情況.因此,接收方第二次再接收的時候,就會將第一次剩余的消息接收到,從而與後續消息粘結住,產生粘包現象.
二. 粘包的解決方案
解決方案(一):粘包問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,所以解決粘包的方法就是圍繞"如何讓發送端在發送數據前,把自己將要發送的字節流總長度讓接收端知曉".
解決步驟:
a. 發送端把"數據長度"傳輸給接收端
b. 接收端把"確認信息"傳輸給發送端
c. 發送端把"全部數據"傳輸給接收端
d. 接收端使用一個死循環接收完所有數據.
服務端:
代碼流程: (服務端是發送端,客戶端是接收端)
a. 服務端接收客戶端的cmd指令
b. 服務端通過subprocess模塊,從電腦系統中,拿到cmd指令返回值
c. 服務端拿到cmd指令返回值的"字節流長度", 並將其傳輸給客戶端
d. 服務端接收來自客戶端的"確認信息"
e. 服務端把"cmd指令返回值"傳輸給客戶端
import socket
import subprocess
server = socket.socket()
ip_port = (‘192.168.15.28‘,8001)
server.bind(ip_port)
server.listen()
conn,addr = server.accept()
while 1:
from_client_cmd = conn.recv(1024).decode(‘utf-8‘) # a.接收來自客戶端的cmd指令
sub_obj = subprocess.Popen(
from_client_cmd, # 客戶端的指令
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
server_cmd_msg = sub_obj.stdout.read() # b.拿到cmd指令返回值 --> stdout接受到的返回值是bytes類型的,並且windows系統的默認編碼為gbk
cmd_msg_len = str(len(server_cmd_msg)) # c.拿到返回值的長度
print("cmd返回的正確信息的長度>>>",cmd_msg_len)
conn.send(cmd_msg_len.encode(‘gbk‘)) # c.把"長度"傳輸給客戶端
from_client_ack = conn.recv(1024).decode(‘utf-8‘) # d.拿到"確認信息"
if from_client_ack == "確認":
conn.send(server_cmd_msg) # e.把"cmd指令返回值"傳輸給客戶端
else:
continue
客戶端:
代碼流程: (服務端是發送端,客戶端是接收端)
a. 用戶輸入cmd指令
b. 客戶端把"cmd指令"傳輸給服務端
c. 客戶端接收cmd指令返回值的"字節流長度"
d. 客戶端把"確認信息"傳輸給服務端
e. 客戶端通過"字節流長度"設置最大可接收數據量,同時接收"cmd指令返回值"
import socket
client = socket.socket()
server_ip_port = (‘192.168.15.28‘,8001)
client.connect(server_ip_port)
while 1:
cmd = input(‘請輸入要執行的指令>>>‘) # a.用戶輸入cmd指令
client.send(cmd.encode(‘utf-8‘)) # b.把"cmd指令"傳輸給服務端
from_server_msglen = int(client.recv(1024).decode(‘gbk‘)) # c.接收cmd指令返回值的"字節流長度"
print(‘接收到的信息長度是>>>‘, from_server_msglen)
client.send(‘確認‘.encode(‘utf-8‘)) # d.把"確認信息"傳輸給服務端
from_server_stdout = client.recv(from_server_msglen).decode(‘gbk‘) # e.設置最大可接收數據量,同時接收"cmd指令返回值"
print(‘接收到的指令返回值是>>>‘, from_server_stdout)
python 粘包現象