粘包及解決方案

1、 粘包

1. 粘包現象

基於tcp協議的socket,客戶端一次接受不完,下一次繼續接受(若是間隔時間相對過長,後續的數據會與以前剩餘的數據黏在一塊兒),send數據時,連續的發送少許的數據(時間間隔很短),這些數據會積壓在一塊兒發送出去.python

2. 粘包現象

  1. 接收方沒有及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包)
  2. 發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據也很小,會合到一塊兒,產生粘包)

3. 粘包產生的緣由

  1. TCP協議是基於數據流的,不管底層是怎樣分段分片的,TCP協議不會根據消息的界限傳輸數據,而是把構成整條消息的數據段排序完成後呈現到內核緩衝區,等到發送方的緩衝區有足夠的數據後才發送一個TCP段
  2. 當socket的傳輸的數據大於接收的數據時,多餘的數據會被放置在緩衝區,接收方的recv在下一次接收數據時,它會繼續接受以前放置在緩衝的數據,而不是發送端最新發送過來的數據

4. 緩衝區

每一個 socket 被建立後,都會分配兩個緩衝區,輸入緩衝區和輸出緩衝區。程序員

write()/send() 並不當即向網絡中傳輸數據,而是先將數據寫入緩衝區中,再由TCP協議將數據從緩衝區發送到目標機器。一旦將數據寫入到緩衝區,函數就能夠成功返回,無論它們有沒有到達目標機器,也無論它們什麼時候被髮送到網絡,這些都是TCP協議負責的事情。shell

TCP協議獨立於 write()/send() 函數,數據有可能剛被寫入緩衝區就發送到網絡,也可能在緩衝區中不斷積壓,屢次寫入的數據被一次性發送到網絡,這取決於當時的網絡狀況、當前線程是否空閒等諸多因素,不禁程序員控制。json

read()/recv() 函數也是如此,也從輸入緩衝區中讀取數據,而不是直接從網絡中讀取。網絡

這些I/O緩衝區特性可整理以下:socket

1.I/O緩衝區在每一個TCP套接字中單獨存在;
2.I/O緩衝區在建立套接字時自動生成;
3.即便關閉套接字也會繼續傳送輸出緩衝區中遺留的數據;
4.關閉套接字將丟失輸入緩衝區中的數據。tcp

2、粘包的解決方案

1. low版

服務端ide

import socket
import subprocess
import struct
server_side = socket.socket()
server_side.bind(("127.0.0.1", 8848))
server_side.listen(5)
while 1:
    conn, addr = server_side.accept()
    while 1:
        try:
            cmd = conn.recv(1024)
            if cmd.decode("utf-8") == 'q':
                break
            obj = subprocess.Popen(cmd.decode("utf-8"),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )
            result = obj.stdout.read() + obj.stderr.read()
            # 製做報頭
            total_size = len(result)
            # 將不固定長度的int數據類型的報頭,轉化成固定長度的4bytes
            total_size_bytes = struct.pack("i", total_size)
            # 發送報頭
            conn.send(total_size_bytes)
            # 發送數據
            conn.send(result)
        except ConnectionResetError:
            break
    conn.close()
server_side.close()

客戶端函數

import socket
import struct
client_side = socket.socket()
client_side.connect(("127.0.0.1", 8848))
while 1:
    to_send = input("請輸入你要執行的命令:")
    if to_send.upper() == "Q":
        client_side.send("q".encode("utf-8"))
        break
    client_side.send(to_send.encode("utf-8"))
    # 接收報頭
    head_bytes = client_side.recv(4)
    # 反解報頭
    total_size = struct.unpack("i", head_bytes)[0]
    # 循環接收數據
    data = b""
    while len(data) < total_size:
        data += client_side.recv(1024)
    print(data.decode("gbk"))
client_side.close()

2. 旗艦版

服務端線程

import socket
import subprocess
import json
import struct
phone = socket.socket()
phone.bind(("127.0.0.1", 8848))
phone.listen(5)
conn, addr = phone.accept()
while 1:
    try:
        cmd = conn.recv(1024)
        obj = subprocess.Popen(cmd.decode("utf-8"),
                               shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE
                               )
        result = obj.stdout.read() + obj.stderr.read()
        result = result.decode("gbk").encode("utf-8")
        # 1.製做報頭
        head_dict = {"md5": "df",
                     "file_name": "新建文件夾",
                     "file_size": len(result)}

        # 2. 將報頭字典轉化成json字符串
        head_dict_json = json.dumps(head_dict)
        # 3. 將json字符串轉化成bytes
        head_dict_json_bytes = head_dict_json.encode("utf-8")
        # 4. 獲取報頭的長度
        head_len = len(head_dict_json_bytes)
        # 5. 將報頭長度轉化成固定的4個bytes
        head_len_bytes = struct.pack("i", head_len)
        # 6. 發送固定的4個字節
        conn.send(head_len_bytes)
        # 7. 發送報頭
        conn.send(head_dict_json_bytes)
        # 8. 發送原數據
        conn.send(result)
    except ConnectionResetError:
        break
conn.close()
phone.close()

客戶端

import socket
import struct
import json
phone = socket.socket()
phone.connect(("127.0.0.1", 8848))
while 1:
    cmd = input("請輸入指令")
    phone.send(cmd.encode("utf-8"))
    # 1. 接收報頭長度
    head_len_bytes = phone.recv(4)
    # 2. 將報頭數字轉化成int類型
    head_len = struct.unpack("i", head_len_bytes)[0]
    # 3. 接收bytes類型的報頭字典
    head_dict_json_bytes = phone.recv(head_len)
    # 4. 將bytes類型的字典轉化成json字符串
    head_dict_json = head_dict_json_bytes.decode("utf-8")
    # 5. 將json字符串轉化成字典
    head_dict = json.loads(head_dict_json)
    # 6. 循環接收原數據
    total_data = b''
    while len(total_data) < head_dict["file_size"]:
        total_data += phone.recv(1024)
    print(total_data.decode("utf-8"))
phone.close
相關文章
相關標籤/搜索