1. 程式人生 > >tcp協議下粘包問題的產生及解決方案

tcp協議下粘包問題的產生及解決方案

ont phone 系統 pac 接收 實的 msg 來源 erro

1、粘包產生原因:

(1)TCP為提高傳輸效率,發送方往往要收集到足夠多的數據後才發送一個TCP段。若連續幾次需要send的數據都很少,通常TCP會根據優化算法(Nagle)把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據;

(2)接收方不知道消息之間的界限,不知道一次性提取多少字節的數據;接收時有字節的限制,如果超過這個限制沒有接收完的會留在

操作系統緩存,下次再執行命令獲取結果時,優先收取上次命令結果殘留的信息;

註:UDP是無連接的,面向消息的,提供高效率服務。不會使用塊的合並優化算法,, 由於UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區)采用了鏈式結構來記錄每一個到達的UDP包,在每個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來說,就容易進行區分處理了。 即面向消息的通信是有消息保護邊界的,

不會出現粘包問題。

2、解決方案

為字節流加上自定義固定長度報頭,報頭中包含字節流長度,然後一次send到對端,對端在接收時,先從緩存中取出定長的報頭,然後再取真實數據。

註:struct 模塊 把一個數字類型轉化為固定長度的bytes

(struct.pack)打包 (struct.unpack)解包

res=(struct.pack(‘i‘,4855524))   #b‘\x04\xe6\xe4\x02‘ 打包
print(res)
print(struct .unpack(‘i‘,res)[0]) #解包

服務端:

import subprocess
import socket
import struct
import json
phone= socket.socket(socket.AF_INET ,socket.SOCK_STREAM )
phone.bind((‘127.0.0.1‘,8080))
phone.listen(5)

while True :
conn,client=phone.accept()
while True :
try:
cmd = conn.recv(1024)
if len(cmd) == 0: break
obj = subprocess.Popen(cmd.decode(‘utf-8‘), shell=True, # 解碼
stdout=subprocess.PIPE, # 正確信息
stderr=subprocess.PIPE # 錯誤信息
)
stdout = obj.stdout.read()
stderr = obj.stderr.read()

#先制作報頭
head_dic= {‘filename‘:‘a.txt‘,
‘total_size‘:len(stdout)+len(stderr),
‘hash‘:‘asdf165485221‘
}
head_json = json.dumps(head_dic)
head_bytes= head_json.encode(‘utf-8‘)
#1、先把報頭的長度打包成四個bytes,然後發送
conn.send(struct.pack(‘i‘,len(head_bytes)))

#2、發送報頭
conn.send(head_bytes)
#3、發送真實數據
conn.send(stdout )
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
phone.close()

客戶端:

import struct
import socket
import json
phone= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone. connect((‘127.0.0.1‘,8080))

while True :
msg = input(‘<<<‘)
if msg == 0:continue
#phone.send(msg.encode(‘utf-8‘))
phone.send(bytes(msg,encoding=‘utf-8‘))

#1、先收4個字節,該4個字節包含報頭的長度 解包
header_len=struct .unpack(‘i‘,phone.recv(4))[0]
#2、再接受報頭
header_bytes=phone.recv(header_len) #通過報頭長度,拿到bytes內容
#從報頭中解析出想要的內容
header_json=header_bytes .decode(‘utf-8‘) #報頭內容解碼得到字符串類型
header_dic=json .loads(header_json) #反序列化得到字典
print(header_dic)
total_size = header_dic[‘total_size‘]


#3、再收真實的數據
recv_size =0 #初始值長度
res=b‘‘ #接收的具體值
while recv_size< total_size:
data= phone.recv(1024)
res+=data # 拼接具體的值
recv_size += len(data) #累加長度
print(res.decode(‘gbk‘)) #收到的信息用GBK解碼
#真實數據的編碼是以當前所在的系統為準的,如果是windows,那麽res.stdout.read()讀出的就是GBK編碼的
,在接收端需要用GBK解碼
phone.close()

tcp協議下粘包問題的產生及解決方案