python之粘包/粘包的解决方案
什么是粘包
粘包就是在数据传输过程中有多个数据包被粘连在一起被发送或接受
服务端:
import socket
import struct
# 创建Socket
Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定服务器和端口号
servers_addr = ('127.0.0.1', 8081)
Socket.bind(servers_addr)
# 监听客户端请求 最大连接数为5
Socket.listen(5)
print('服务器启动成功,等待客户端连接...')
# 接受数据
client_socket, client_addr = Socket.accept()
print('与客户端建立连接', client_addr)
client_socket.setblocking(False)
# 数据交换
while True:
data = client_socket.recv(10880) # 最大1024字节
if len(data) < 1:
print('关闭服务')
break
# 接受客户器端传来的数据
print(data.decode())
# 向客户端返回数据
client_socket.sendall(data)
break
Socket.close()
客户端:
import socket
import subprocess
# 获取cmd指令
cmd_from_client = 'ipconfig'
cmd_msg = subprocess.Popen(cmd_from_client,
shell=True, # 使用shell命令
stdout=subprocess.PIPE, # 管道一:输出结果
stderr=subprocess.PIPE # 管道二:输出错误信息
)
msg_one = cmd_msg.stdout.read().decode('gbk')
msg_two = cmd_msg.stderr.read().decode('gbk')
msg = msg_one + msg_two
# 创建Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 服务器地址和端口
server_address = ('localhost', 8081)
# 连接服务器
client_socket.connect(server_address)
print('已连接到服务器:', server_address)
while True:
# 发送数据
# message = input('>>>>')
client_socket.sendall(msg.encode())
# 接收响应
response = client_socket.recv(1024)
print('服务器响应:', response.decode())
break
client_socket.close()
案例中使用了subprocess
模块输出了ip信息,在服务端打印的数据中可以看到内容是能够正常输出的
但是根据客户端的控制台显示数据在返回时被截断了
其实原因很简单:
response = client_socket.recv(1024)
数据在服务端中能一次性的接收,但由于客户端只能接受1024,所以就不会从缓存中一下取完大于1024的那部分数据,其实不管是客户端还是服务端,recv()
的缓存区大小都是可控的,但是发送方发送了一个 10KB 的数据包,而接收方使用 recv(1024)
只能一次接收最多 1KB 的数据,这样就需要多次调用 recv()
来接收完整的数据,可能会引发粘包问题
客户端
import socket
# 创建 Socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 服务器地址和端口
server_address = ('localhost', 8081)
# 连接服务器
client_socket.connect(server_address)
print('已连接到服务器:', server_address)
# 发送数据包
message1 = 'Hello'
message2 = 'World'
# 连续发送两个数据包
client_socket.sendall(message1.encode())
client_socket.sendall(message2.encode())
# 关闭连接
client_socket.close()
服务端
import socket
# 创建 Socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定服务器地址和端口
server_address = ('localhost', 8081)
server_socket.bind(server_address)
# 监听客户端请求
server_socket.listen(1)
print('等待客户端连接...')
while True:
# 接受连接
client_socket, client_addr = server_socket.accept()
print('与客户端建立连接:', client_addr)
# 接收数据
data = client_socket.recv(1024) # 接收数据包
received_data = data.decode()
# 处理接收到的数据
print('接收到数据:', received_data)
理想情况:
等待客户端连接...
与客户端建立连接: ('127.0.0.1', 61127)
接收到数据: Hello
接收到数据: World
实际情况:
等待客户端连接...
与客户端建立连接: ('127.0.0.1', 61127)
接收到数据: HelloWorld
导致粘包的原因
1.缓冲区大小限制:在TCP传输中,由于数据过大,超出缓存区大小限制,导致接收方不能接收到所有的数据包,造成了数据包的截断或丢失
2.底层协议特性:底层传输协议如 TCP 是面向流的,不保留消息边界。TCP 协议会将数据流切分为适当大小的数据块进行传输,因此无法保证每个数据包的边界
3.数据发送速度过快:发送方连续发送数据包,而接收方无法及时处理,导致多个数据包在接收缓冲区中堆积
解决方案:struct模块
利用pack()
方法将任意长度的 数字 打包成新的数据
再用unpack()
方法将固定长度的 数字 解包成打包前数据真实的长度
pack()
方法 第一个参数是格式,第二个参数是整数(数据的长度),返回值是一个新的数据unpack()
方法 第一个参数是格式,第二个参数是pack()
方法打包后生成的新数据,返回值是一个元组,元组中放着打包前数据真实的长度
import struct
msg_one = '你好'
msg_two = ('struct 是 Python 标准库中的一个模块,用于进行字节与数据类型之间的相互转换。它提供了'
'一组函数来打包(pack)和解包(unpack)数据,使得数据在网络传输或文件存储时能够以二进制形式进行处理。')
total = len(msg_one) + len(msg_two) # 106
# 将数据打包
res = struct.pack('i', total)
# 解包数据
un_res = struct.unpack('i', res)
print(len(res)) # 4
print(res) # bytes类型: b'j\x00\x00\x00'
print(un_res) # 元组类型: (106,)
粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据
客户端
import socket
import struct
# 创建 Socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 服务器地址和端口
server_address = ('localhost', 8081)
# 连接服务器
client_socket.connect(server_address)
print('已连接到服务器:', server_address)
# 发送数据包
msg = b'helloworld'
data = struct.pack('i', len(msg))
# 先发送报头
client_socket.send(data)
# 发送真实数据
client_socket.send(msg)
服务端
import socket
import struct
# 创建 Socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定服务器地址和端口
server_address = ('localhost', 8081)
server_socket.bind(server_address)
# 监听客户端请求
server_socket.listen(1)
print('等待客户端连接...')
while True:
# 接受连接
client_socket, client_addr = server_socket.accept()
print('与客户端建立连接:', client_addr)
# 接收数据
data = client_socket.recv(1024) # 接收数据包
received_data = struct.unpack('i', data)
data_len = received_data[0]
real_data = client_socket.recv(data_len)
# 处理接收到的数据
print('接收到数据:', real_data.decode('utf8'))
根据该原理改进案例代码
客户端
import socket
import struct
# 创建Socket
Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定服务器和端口号
servers_addr = ('127.0.0.1', 8082)
Socket.bind(servers_addr)
# 监听客户端请求 最大连接数为5
Socket.listen(5)
print('服务器启动成功,等待客户端连接...')
# 接受数据
client_socket, client_addr = Socket.accept()
print('与客户端建立连接', client_addr)
# client_socket.setblocking(False)
# 数据交换
while True:
# 接受报头
header = client_socket.recv(4) # 最大1024字节
if len(header) < 1:
print('关闭服务')
break
data_len = struct.unpack('i', header)[0]
print(data_len)
# 接受真实数据
real_data = client_socket.recv(data_len)
print(real_data.decode('gbk'))
# 向客户端返回数据
client_socket.send(real_data)
服务端
import socket
import struct
import subprocess
# 获取cmd指令
cmd_from_client = 'ipconfig'
cmd_msg = subprocess.Popen(cmd_from_client,
shell=True, # 使用shell命令
stdout=subprocess.PIPE, # 管道一:输出结果
stderr=subprocess.PIPE # 管道二:输出错误信息
)
msg_one = cmd_msg.stdout.read().decode('gbk')
msg_two = cmd_msg.stderr.read().decode('gbk')
msg = msg_one + msg_two
# 创建Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 服务器地址和端口
server_address = ('localhost', 8082)
# 连接服务器
client_socket.connect(server_address)
print('已连接到服务器:', server_address)
while True:
# 先发报头
data_len = struct.pack('i', len(msg))
client_socket.send(data_len)
# 发送数据
client_socket.send(msg.encode('gbk'))
# 接收响应
response = client_socket.recv(data_len[0])
print('服务器响应:', response.decode('gbk'))