解决粘包问题
一、解决粘包问题方式一
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
1.1 服务器
import socket, subprocess
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)
while True:
conn, addr = server.accept()
print('start...')
while True:
cmd = conn.recv(1024)
print('cmd:', cmd)
obj = subprocess.Popen(cmd.decode('utf8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
stdout = obj.stdout.read()
if stdout:
ret = stdout
else:
stderr = obj.stderr.read()
ret = stderr
ret_len = len(ret)
conn.send(str(ret_len).encode('utf8'))
data = conn.recv(1024).decode('utf8')
if data == 'recv_ready':
conn.sendall(ret)
conn.close()
server.close()
1.2 客户端
import socket
import struct
soc = socket.socket()
soc.connect(('127.0.0.1', 8001))
while True:
in_s = input("请输入要执行命令:")
soc.send(in_s.encode('utf-8'))
head = soc.recv(4)
lengs = struct.unpack('i', head)[0]
count = 0
data_total = b""
while count < lengs:
if lengs < 1024:
# 如果接收的数据小于1024,直接接收数据的大小
data = soc.recv(lengs)
else:
# 如果接收的数据大于1024
if lengs - count > 1024:
# 总数据长度减去count(目前收到多少, count就是多少), 如果还大于1024,在收1024
data = soc.recv(1024)
else:
# 总数据长度减去count(目前收到多少,count就是多少), 如果小于1024, 只收剩下的部分即可
data = soc.recv(lengs- count)
data_total += data
count += len(data)
print(data_total.decode('gbk'))
缺点:
程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗
二、补充struct模块
2.1 简单实用
[
import struct
import json
# 'i'是格式
try:
obj = struct.pack('i', 1222222222223)
except Exception as e:
print(e)
obj = struct.pack('i', 1222)
print(obj, len(obj))
'i' format requires -2147483648 <= number <= 2147483647
b'xc6x04x00x00' 4
res = struct.unpack('i', obj)
print(res[0])
1222
三、解决粘包问题终结版
解决粘包问题的核心就是:为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据。
3.1 使用struct模块创建报头
import json
import struct
header_dic = {
'filename': 'a.txt',
'total_size':
111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222223131232,
'hash': 'asdf123123x123213x'
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
print(len(header_bytes))
# 'i'是格式
obj = struct.pack('i', len(header_bytes))
print(obj, len(obj))
223
b'xdfx00x00x00' 4
res = struct.unpack('i', obj)
print(res[0])
223
3.2服务端
import socket
import subprocess
import struct
soc = socket.socket()
soc.bind(('127.0.0.1', 8001))
soc.listen(3)
while True:
print("等待客户端连接")
conn, addr = soc.accept()
print("客户端连接上了", addr)
while True:
try:
data = conn.recv(1024)
if len(data) == 0:
break
print(data)
obj = subprocess.Popen(data.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# 执行正确的结果,b格式gbk编码(windows平台)
msg = obj.stdout.read()
"""
发送的时候需要先把长度计算出来
头必须是固定长度
先发4位,头的长度
"""
import json
dic = {'size': len(msg)}
# 文件的长度,将字典序列化成字符串,进行二进制编码
dict_bytes = json.dumps(dic).encode('utf-8')
print("dict_bytes", dict_bytes)
# head count是四个字节长度,统计字典的长度,转换成二进制
head_count = struct.pack('i', len(dict_bytes))
print(dic)
print('head_count', head_count)
# 发送头的长度 4个字节
conn.send(head_count)
# 发送头部内部
conn.send(dict_bytes)
# 发送内容
conn.send(msg)
print(msg)
except Exception as e:
print(e)
conn.close()
break
soc.close()
3.3 客户端
import socket
import struct
import json
soc = socket.socket()
soc.connect(('127.0.0.1',8001))
while True:
in_s = input("请输入要执行的命令")
soc.send(in_s.encode('utf-8'))
# 头部字典的长度, 读取字典的长度
head_dic_len = soc.recv(4)
print(head_dic_len)
# 解除真正的长度,对获取的字典长度进行解码,得到实际的长度
head_l = struct.unpack('i', head_dic_len)[0]
# byte 字典长度,读取字典的内容
dic_byte = soc.recv(head_l)
# 真正受到的头部字典,将读取的字典反序列化, 得到真正的字典
head = json.loads(dic_byte)
print(head)
lengs = head['size']
count = 0
data_total = b""
print(lengs)
while count < lengs:
if lengs < 1024: # 如果接受的数据小于1024 ,直接接受数据大小
data = soc.recv(lengs)
else: # 如果接受的数据大于1024
if lengs - count >= 1024: # 总数据长度-count(目前收到多少,count就是多少) 如果还大于1024 ,在收1024
data = soc.recv(1024)
else: # 总数据长度-count(目前收到多少,count就是多少) 如果小于1024,只收剩下的部分就可
data = soc.recv(lengs - count)
data_total += data
count += len(data)
print(data_total.decode('gbk'))
四、总结
解决粘包问题:就是转换成发送数据和接收数据的格式,
发送数据:发送数据,先发送数据的长度,然后在发送真实数据的字节数;
接收数据:接收真实数据的长度,然后在安置字节长度接收数据;