解决粘包问题(python)

在传输数据消息时因为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时)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值