1. 程式人生 > >python 粘包現象

python 粘包現象

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 粘包現象