【Python】Tcp Socket處理粘包與分包問題
阿新 • • 發佈:2019-01-25
測試環境
- win10
- python3.6
粘包和分包
- 粘包:傳送方傳送兩個字串”hello”+”world”,接收方卻一次性接收到了”helloworld”
- 分包:傳送方傳送字串”helloworld”,接收方卻接收到了兩個字串”hello”和”world”
解決方案
- 自定義傳輸協議:訊息頭 + 訊息體
- 其中訊息頭定長,且包含訊息體的長度
具體操作
- 關鍵:一個FIFO佇列作為資料緩衝區,用於接收資料和判斷
- 流程:
- 把從socket接收到的資料,push進佇列
- 判斷資料的長度是否大於訊息頭(自定義長度)的長度,如果成立則繼續下一步,否則跳出迴圈繼續接收資料
- 如果當前資料長度大於訊息頭,則讀取訊息頭裡訊息體的長度,判斷當前資料長度是否大於訊息頭+訊息體的長度
- 如果當前資料大於訊息頭+訊息體的長度,則處理資料,然後pop出佇列
程式碼實現
- server.py
import socket
import struct
HOST = ''
PORT = 1234
# FIFO訊息佇列
dataBuffer = bytes()
# 自定義訊息頭的長度
headerSize = 12
# 定義資料包的個數
sn = 0
# 正文資料處理
def dataHandle(headPack, body):
global sn
sn += 1
print(f"第{sn}個數據包")
print(body.decode())
print("\n")
if __name__ == '__main__':
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)
if data:
# 把資料存入緩衝區,類似於push資料
dataBuffer += data
while True:
if len(dataBuffer) < headerSize:
print("資料包(%s Byte)小於訊息頭部長度,跳出小迴圈" % len(dataBuffer))
break
# 讀取包頭
# struct中:!代表Network order,3I代表3個unsigned int資料
headPack = struct.unpack('!3I', dataBuffer[:headerSize])
bodySize = headPack[1]
# 分包情況處理,跳出函式繼續接收資料
if len(dataBuffer) < headerSize+bodySize :
print("資料包(%s Byte)不完整(總共%s Byte),跳出小迴圈" % (len(dataBuffer), headerSize+bodySize))
break
# 讀取訊息正文的內容
body = dataBuffer[headerSize:headerSize+bodySize]
# 資料處理
dataHandle(headPack, body)
# 資料出列
dataBuffer = dataBuffer[headerSize+bodySize:] # 獲取下一個資料包,類似於把資料pop出
- client.py
import socket
import time
import struct
import json
host = "localhost"
port = 1234
ADDR = (host, port)
if __name__ == '__main__':
client = socket.socket()
client.connect(ADDR)
# 正常資料包定義
ver = 1
body = json.dumps(dict(hello="world"))
print(body)
cmd = 101
header = [ver, body.__len__(), cmd]
headPack = struct.pack("!3I", *header)
sendData1 = headPack+body.encode()
# 分包資料定義
ver = 2
body = json.dumps(dict(hello="world2"))
print(body)
cmd = 102
header = [ver, body.__len__(), cmd]
headPack = struct.pack("!3I", *header)
sendData2_1 = headPack+body[:2].encode()
sendData2_2 = body[2:].encode()
# 粘包資料定義
ver = 3
body1 = json.dumps(dict(hello="world3"))
print(body1)
cmd = 103
header = [ver, body1.__len__(), cmd]
headPack1 = struct.pack("!3I", *header)
ver = 4
body2 = json.dumps(dict(hello="world4"))
print(body2)
cmd = 104
header = [ver, body2.__len__(), cmd]
headPack2 = struct.pack("!3I", *header)
sendData3 = headPack1+body1.encode()+headPack2+body2.encode()
# 正常資料包
client.send(sendData1)
time.sleep(3)
# 分包測試
client.send(sendData2_1)
time.sleep(0.2)
client.send(sendData2_2)
time.sleep(3)
# 粘包測試
client.send(sendData3)
time.sleep(3)
client.close()
效果
Connected by ('127.0.0.1', 29771)
第1個數據包
ver:1, bodySize:18, cmd:101
{"hello": "world"}
資料包(0 Byte)小於訊息頭部長度,跳出小迴圈
資料包(14 Byte)不完整(總共31 Byte),跳出小迴圈
第2個數據包
ver:2, bodySize:19, cmd:102
{"hello": "world2"}
資料包(0 Byte)小於訊息頭部長度,跳出小迴圈
第3個數據包
ver:3, bodySize:19, cmd:103
{"hello": "world3"}
第4個數據包
ver:4, bodySize:19, cmd:104
{"hello": "world4"}