tcp协议下粘包问题的产生及解决方案

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()

猜你喜欢

转载自www.cnblogs.com/quqinchao/p/9290646.html