Python--粘包问题及基于 socket 编程解决粘包问题

一、粘包问题介绍与解决思路

服务端–连续接受三次消息,并且打印这三次消息

客户端–连续发送三个消息

import socket
from socket import SOL_SOCKET,SO_REUSEADDR


server = socket.socket()

server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)  # 就是它,在bind前加
server.bind(('127.0.0.1',8081))

server.listen(2)
conn, addr = server.accept()

data = conn.recv(1024)
print(data)
data1 = conn.recv(1024)
print(data1)
data2 = conn.recv(1024)
print(data2)

conn.close()
import socket

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

client.send(b'data1')
client.send(b'data2')
client.send(b'data3')

client.close()

在这里插入图片描述

会发现,我们一次性将数据全部收了过来,并不是一次一次的取来,原因就在于—服务端每次是 1024 个字节接收的

TCP协议的特点–会将数据量比较小并且时间间隔问题比较短的数据整合起来一起发送!~

所以,基于上述原因,我们可以将客户端每次接受的字节数修改成 5 个字节

import socket
from socket import SOL_SOCKET,SO_REUSEADDR


server = socket.socket()

server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)  # 就是它,在bind前加
server.bind(('127.0.0.1',8081))

server.listen(2)
conn, addr = server.accept()

data = conn.recv(5)
print(data)
data1 = conn.recv(5)
print(data1)
data2 = conn.recv(5)
print(data2)

conn.close()

在这里插入图片描述

问题产生的原因在于:recv 括号内的我们并不能预料到每次接受的数据的大小是多少,并且 recv 每次接收数据的的大小也有限制

解决思路:如果我们每次都能精准的获取本次要接受的数据的大小,就能够完美的接收

二、struct 模块介绍

2.1 功能一:pack()方法

pack()方法将任意长度的 数字 打包成新的数据,这个新数据的长度是固定

pack()方法 第一个参数是格式,第二个参数是整数(数据的长度)

—返回值是一个新的数据

import struct

data1 = 'hello world'
data2 = 'hello bao bei baby honey darling'
print(len(data1)) # 11
print(len(data2)) # 32
res1 = struct.pack('i', len(data1))
res2 = struct.pack('i', len(data2))
print(len(res1)) # 4
print(len(res2)) # 4
'结果为:'
11
32
4
4

2.2 功能二:unpack()方法

unpack()方法将固定长度的 数字 解包成打包前数据真实的长度

unpack()方法 第一个参数是格式,第二个参数是 pack()方法打包后生成的新数据

返回值是一个元组,元祖中放着打包前数据真实的长度

import struct

data1 = 'hello world'
data2 = 'hello bao bei baby honey darling'
res1 = struct.pack('i', len(data1))
res2 = struct.pack('i', len(data2))

x = struct.unpack('i', res1)
y = struct.unpack('i', res2)
print(x) # (11,)
print(y) # (32,)

2.3 基于 struct 模块–解决粘包问题的思路

  1. 先将真实的数据打包成固定长度的包;
  2. 先把固定长度的包发送过去;
  3. 接收后解包得到真实数据的长度;
  4. 接收真实数据

对于不同的模式打包后的新数据的长度不同,但是不论什么模式都有数据长度的限制

小问题:如果打包的数据量非常的巨大,就会导致无法打包

那么,转换思路,可以不直接打包原始的数据,而是打包一个数据的字典

data_dict = {
  'file_name' = '学习资料.zip',
  'file_describe' = '里面放的都是好东西,500g 的学习资料',
  'file_size' = 666666666666666
}

最终解决方案:

发送方:

  1. 首先构建一个数据字典–包括名称,简介,数据的大小
  2. 将字典打包成固定长度的包
  3. 将字典的包发送过去
  4. 发送 真实的字典数据
  5. 发送 真实的数据

接收方:

  1. 先接受字典的包,解析出字典的长度
  2. 接受 字典数据 , 解析出真实数据的各种信息,并且获得真实数据的长度
  3. 接受真实数据

三、基于 socket 编程-解决粘包问题示例

服务端:

import socket
import os
import struct
import json


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

conn, addr = server.accept()

data_dict = {
    'file_name':'xxx刺激.txt',
    'file_desc':'封控一个多月了',
    'file_size':os.path.getsize(r'04 struct模块.py')
}
# 1.先打包字典
dict_json_str = json.dumps(data_dict)
dict_bytes = dict_json_str.encode('utf8')
dict_package_header = struct.pack('i', len(dict_bytes))
# 2.发送报头
conn.send(dict_package_header)
# 3.发送字典
conn.send(dict_bytes)
# 4.发送真实数据
with open(r'04 struct模块.py', 'rb') as f:
    for line in f:
        conn.send(line)

客户端:

import socket
import struct
import json

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

# 1.先接收固定长度的字典的报头
dict_header_len = client.recv(4)
# 2.解析出字典的真实长度
dict_real_len = struct.unpack('i', dict_header_len)[0]
# 3.接收字典数据
dict_data_bytes = client.recv(dict_real_len)
dict_data = json.loads(dict_data_bytes)
print(dict_data)
# 4.循环接收文件数据 不要一次性接收
recv_size = 0
with open(dict_data.get('file_name'),'wb') as f:
    while recv_size < dict_data.get('file_size'):
        data = client.recv(1024)
        recv_size += len(data)
        f.write(data)
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 TCP 网络通信中,发送端粘包问题是指发送端在发送数据时,由于数据量较小或发送速度较快,导致多个数据包被合并成一个接收到的数据包的现象。这会给数据解析和处理带来困难。 为了解决发送端粘包问题,可以采取以下几种方法: 1. 使用固定长度消息:将每个消息的长度固定为一个固定值,例如每个消息的长度为固定的10个字节。接收端在接收数据时,按照固定长度进行分割解析。 2. 使用分隔符:在每个消息的末尾添加一个特殊的分隔符,例如换行符 '\n'。接收端在接收数据时,根据分隔符进行分割解析。 3. 使用消息头:在每个消息的开头添加一个消息头,用来记录该消息的长度。接收端在接收数据时,先读取消息头获取消息长度,然后根据长度读取对应数量的数据进行解析。 示例代码如下所示: ```python import socket def send_message(sock, message): # 添加消息头记录消息长度 message = f"{len(message)}:{message}" # 发送消息 sock.sendall(message.encode()) def receive_message(sock): # 接收数据 data = sock.recv(1024).decode() # 找到消息头的分隔符索引 separator_index = data.find(':') if separator_index != -1: # 获取消息长度 message_length = int(data[:separator_index]) # 获取消息内容 message = data[separator_index+1:separator_index+1+message_length] return message else: return None # 创建 TCP socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接服务器 sock.connect(('localhost', 8888)) # 发送消息 send_message(sock, "Hello") send_message(sock, "World") # 接收消息 message1 = receive_message(sock) message2 = receive_message(sock) print(message1) # 输出:Hello print(message2) # 输出:World # 关闭连接 sock.close() ``` 在上述示例代码中,发送端在发送消息时先添加消息头记录消息的长度,接收端在接收数据后根据消息头解析出消息的长度和内容。这样即可解决发送端粘包问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值