python网络编程-粘包问题的解决 代码

我们使用tcp协议的时候有时会出现一些问题,

就比如我同时发送了3次数据,但是在另外一边缺只收到了一次,它把三次数据都和在了一起,

服务端(接收)

import socket


server = socket.socket()
server.bind(('127.0.0.1',18080))  # 绑定ip和端口
server.listen(2)    # 半连接池



conn,addr = server.accept() # 等别人来
data = conn.recv(1024)      # 如果conn 没了,就会报错
print('第一次接收--')
print(data.decode('utf-8'))

data = conn.recv(1024)      # 如果conn 没了,就会报错
print('第二次接收---')
print(data.decode('utf-8'))

data = conn.recv(1024)      # 如果conn 没了,就会报错
print('第三次接收---')
print(data.decode('utf-8'))

客户端(发送)

import socket

client = socket.socket()
client.connect(('127.0.0.1',18080))     # 连接

client.send(b'11')    # 发送第一次
client.send(b'22')    # 第二次发送
client.send(b'33')    # 第三次发送

输出结果:

>>>
第一次接收-- 112233 第二次接收--- 第三次接收---

这个就是tcp粘包问题

解决这个问题的关键就是设置recv的长度,recv就是获取数据的长度,所以我们需要得到被发送数据的长度,但是我们又有可能不知道数据长度的长度,除非我们能固定这个数据的长度。这里我们可以引入一个模块:他就能固定数据的长度

struct 模块

struct 模块
    # 对数据进行打包处理,被打包后的数据是固定长度,,我们只需要接收这个固定长度就好,然后把这个包解开,就能得到真正的数据的长度
    pack # 打包数据
    unpack  # 解包数据

这时我们就不止能发送数据了,还可以把数据的数据信息(名称,数据长度,大小),都打包到一个字典当中,我们直接把字典序列化,然后把字典的报头(数据长度),和字典一起传过去就好了。

服务端(发送)
1:生成一个字典
2:制作字典的报头
JSON序列化
编码
统计长度
3:发送字典的报头
4:发送字典
5:发送真实的数据

客户端(接收):
1:先接受固定长度,4
个字节的报头(几个字节取决于struct打包的报头长度)
2:解析获取的字典数据长度
unpack(...)[0]
3:接受字典数据
解码
反序列化
4:接收真实数据

这样一来我们就成功解决了粘包问题


客户端(接收)

import socket
import struct
import json

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:
        continue
    client.send(msg)
    # 1.先接受字典报头
    header_dict = client.recv(4)

    # 2.解析报头 获取字典的长度
    dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0

    # 3.接收字典数据
    dict_bytes = client.recv(dict_size)
    dict_json = json.loads(dict_bytes.decode('utf-8'))

    # 4.从字典中获取信息
    print(dict_json)    # 这个是字典
    recv_size = 0       # 这个是设置下载总量的初始值,下载多少就加多少
    real_data = b''     # 这个是下载的东西,下载了什么,就把他加进去
        # 如果一直加这个数据的花,这个数据如果很大,会不会把内存弄爆炸?
    while recv_size < dict_json.get('file_size'):  # 如果已经接受的数据总量不大于被接受数据的总量时,跳出循环(接收循环)
    # while recv_size < dict_json.['file_size']:  # 不推荐用这个方法,最好用get,这个可能会报错
        data = client.recv(1024)    # 一次性接收多大
        real_data += data           # 把数据加上去
        recv_size += len(data)      # 把已经下载的数据长度加在下载数据长度上面
    print(real_data.decode('gbk'))  # 打印已经下载好的数据

服务端(发送)

import socket
import subprocess
import struct
import json

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    conn, addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            if len(cmd) == 0: break
            cmd = cmd.decode('utf-8')
            obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            res = obj.stdout.read() + obj.stderr.read()

            d = {'file_size': len(res)}  # 这个是文件大小
            # d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'}
            json_d = json.dumps(d)  # 格式化

            # 1.先制作一个字典的报头
            header = struct.pack('i', len(json_d))
            print(header,'---',len(header))
            # 2.发送字典报头
            conn.send(header)  # 发送字典的文件大小

            # 3.发送字典
            conn.send(json_d.encode('utf-8'))  # 发送字典

            # 4.再发真实数据
            conn.send(res)

            # conn.send(obj.stdout.read())
            # conn.send(obj.stderr.read())
        except ConnectionResetError:
            break
    conn.close()