解决粘包问题

解决粘包问题

一、解决粘包问题方式一

问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

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 简单实用

[124-解决粘包问题-struct模块参数.png?x-oss-process=style/watermark

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'\xc6\x04\x00\x00' 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'\xdf\x00\x00\x00' 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'))

四、总结

解决粘包问题:就是转换成发送数据和接收数据的格式,

发送数据:发送数据,先发送数据的长度,然后在发送真实数据的字节数;

接收数据:接收真实数据的长度,然后在安置字节长度接收数据;

posted @ 2019-09-13 15:53  RandySun  阅读(763)  评论(0编辑  收藏  举报