在传输数据消息时因为TCP协议使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,这样,接收端就难于分辨出来了,所以会产生粘包效果。
在这种情况下我们需要制作一个报头来告诉接收端我们要发送的数据的长度,来方便接收端接收。
第一步:制作固定长度的报头
header_dic = {
'filename': 'a.txt',
'total_size': len(stdeer) + len(stdout)
} # 字典方便储存数据
header_json = json.dumps(header_dic) # 把字典转换成js格式(字符串类型)
header_bytes = header_json.encode('gbk')
(在这里我们基于上次模拟ssh远程执行命令的代码上改动)
这个用字典存放我们发送数据的长度外加一些其他以后可能会用到的其他内容。因为字典无法用encode()转换成字节,所以我们要用json模块把字典转换成js格式。
第二步:把报头打包发送长度
conn.send(struct.pack('i', len(header_bytes)))
第三步:发送报头
conn.send(header_bytes)
第四步:发送需要给接收端的数据
conn.send(stdout)
conn.send(stdeer)
这样结合之前的模拟ssh远程模拟命令之后的总代码:
1、服务端
import socket
import subprocess
import struct # 制作报头的模块
import json # 转换数据格式(序列化)
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 修复端口被占用的问题。
phone.bind(('127.0.0.1', 8848))
phone.listen(5) # 监听,挂起连接数
while True:
conn, client = phone.accept() # 建立连接
while True:
try:
# 收命令
cmd = conn.recv(8096) # 长度足够收取命令
# 执行命令、拿到结果
obj = subprocess.Popen(cmd.decode('gbk'), shell=True,
stdout=subprocess.PIPE, # 存放正确的通道
stderr=subprocess.PIPE) # 存放错误的通道
stdout = obj.stdout.read() # 把里面的内容读出来放在这里
stdeer = obj.stderr.read()
# 把命令结果给客户端
# 第一步:制作固定长度的报头
header_dic = {
'filename': 'a.txt',
'total_size': len(stdeer) + len(stdout) # 要发送数据的字节长度
} # 字典方便储存数据
header_json = json.dumps(header_dic) # 把字典转换成js格式(字符串类型)
header_bytes = header_json.encode('gbk')
# 第二步:先发送报头的长度
conn.send(struct.pack('i', len(header_bytes)))
# 第三步:再发报头
conn.send(header_bytes)
# 第四步:发送真实数据
conn.send(stdout)
conn.send(stdeer)
except ConnectionResetError as err:
break
conn.close()
phone.close()
2、客户端(接收端)
import socket
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8848))
while True:
# 发命令
cmd = input('>>:').strip()
if not cmd:
continue
phone.send(cmd.encode("gbk"))
# 拿到命令结果
# 第一步:先收取报头的长度
obj = phone.recv(4)
header_size = struct.unpack('i', obj)[0]
# 第二步:再收报头
header_bytes = phone.recv(header_size)
# 第三步:从报头中间解析出对真是数据的描述信息
header_json = header_bytes.decode('gbk')
header_dic = json.loads(header_json)
print(header_dic)
total_size = header_dic['total_size']
# 第三步:接受真实的数据
recv_size = 0
recv_data = b''
while recv_size < total_size:
res = phone.recv(1024)
recv_data += res
recv_size += len(res)
print(recv_data.decode("gbk"))
phone.close()
在客户端第三步中的循环就是确定避免了1024这个坑,这样可以一直循环到收完一个命令得出的全部数据(在数据量大于1024时)。