解决粘包问题
Posted 每天进步一点点
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解决粘包问题相关的知识,希望对你有一定的参考价值。
解决粘包问题
在OSI七层模型中,我们可以看到,数据包从应用层产生,会在应用层生成一个头文件+数据的包传递给下一层,在下一层中它会认识这个包就是一个整体,然后会在这上面再重新添加一个包,直到物理层发送电信号,到了服务端一层层的解包,(https://www.processon.com/view/5b124aa3e4b068c2520f1292)
那么如果我们想解决粘包的问题,首先最重要的就是要知道这个数据包到底有多大,那么应该怎么知道呢?
struct模块
struct模块是Python中内置模块,它用来在C语言中的结构体与Python中的字符串之间进行转换,数据一般来自网络或文件
常用方法
struct.pack(fmt,v1)
返回的是一个字符串,是参数按照fmt数据格式组合而成
struct.unpack(fmt.string)
按照给定数据格式解开(通常都是由struct.pack进行打包)数据,返回值是一个tuple
格式符
具体请参考:(https://blog.csdn.net/djstavav/article/details/77950352)
我们目前能用到的就是fmt=i,固定返回的是4个bytes,那么我们就可以和OSI七层模型一样,在还没有发送自己的真实数据之前,先告诉客户端我会发送多大的数据,也就是先发一个头部文件过去,然后客户端就开始进入循环,一直把这个数据接收完才可以,看以下代码
简单版本
# 服务端
# 导入模块
import socket
import subprocess
import struct
# 创建socket对象,监听连接
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind((‘127.0.0.1‘,8009))
server.listen(5)
while True:
# 获取客户端连接对象以及IP地址
conn,client_addr = server.accept()
print(‘客户端地址:‘,client_addr)
try:
cmd = conn.recv(1024) # 接收1024bytes
obj = subprocess.Popen(cmd.decode(‘utf-8‘),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
total_size = len(stdout) + len(stderr) # 统计总大小
# 制作固定长度的报头
header = struct.pack(‘i‘,total_size)
# 发送报头给客户端
conn.send(header)
# 发送真实的数据
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError as e: # 如果客户端突然断开连接,则break
break
conn.close() # 断开本次连接
server.close()
# 客户端
import socket
import struct
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect((‘127.0.0.1‘,8009)) # 连接服务器地址
while True:
cmd = input(‘>>>‘).strip()
if not cmd:continue
client.send(cmd.encode(‘utf-8‘)) # 以UTF-8的编码发送
header = client.recv(4) # 接收头部固定4个bytes
total_size = struct.unpack(‘i‘,header)[0] # 获取真实数据的长度
recv_size = 0 # 接收的大小
recv_data = b‘‘ # 字符串的拼接
while recv_size < total_size: # 如果接收的大小<总长度
res = client.recv(1024) # 接收bytes
recv_data += res # 拼接
recv_size += len(res) # 获取当前已接收真实数据的长度
print(recv_data.decode(‘GBK‘)) # GBK解码
client.close() # 关闭本地连接
终极版本
# 服务端
# 导入模块
import socket
import subprocess
import json
import struct
# 创建对象、绑定、监听
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind((‘127.0.0.1‘,8889))
server.listen(5)
print(‘starting......‘)
# 与客户端建立连接
while True:
conn,addr = server.accept()
print(‘客户端地址:‘,addr)
while True:
try:
# 1.收命令
cmd = conn.recv(1024)
# 2.执行命令,得到命令的结果
obj = subprocess.Popen(cmd.decode(‘utf-8‘),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
# 3.把命令的结果返回给客户端
# 3.1制作固定长度的报头
header_dict = {
‘filename‘:‘a.txt‘,
‘md5‘:‘dd0a833f9512f1145d9f6869c42ec902‘,
‘total_size‘:len(stdout)+len(stderr)
}
# print(len(header_dict))
header_json = json.dumps(header_dict) # 序列化文件
header_bytes = header_json.encode(‘utf-8‘) # 转换成为二进制字节码
# print(header_bytes)
# 3.2先发送报头的长度
header = struct.pack(‘i‘,len(header_bytes)) # 将转换的字节码通过pack进行打包得到4个bytes
print(len(header))
conn.send(header)
conn.send(header_bytes)
print(‘已经发送文件‘)
# 3.3再发送真实的数据
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
server.close()
# 客户端
# 导入模块
import socket
import struct
import json
# 创建套接字对象、连接服务器端
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect((‘127.0.0.1‘,8889))
while True:
# 1.发送命令
cmd = input(‘>>>>>‘).strip()
if not cmd:continue
client.send(cmd.encode(‘utf-8‘))
# 2.拿到命令的结果并打印
# 2.1先收报头的长度
obj = client.recv(4) # 接收4个bytes
header_size = struct.unpack(‘i‘,obj)[0] # 获取报头的长度,即header_dict的长度
# 2.2再收报头
header_bytes = client.recv(header_size) # 将header_dict收到后进行转码,得到了json文件
# 2.3从报头中解析出对真实数据的描述信息
header_json = header_bytes.decode(‘utf-8‘)
# print(len(header_json))
print(‘client已经收到文件‘)
header_dict = json.loads(header_json) # 从内存中拿到json文件
print(header_dict)
total_size = header_dict[‘total_size‘] # 获取真实数据的大小
# 3.接收真实的数据
recv_size = 0 # 已经接收的数据
recv_data = b‘‘ # 字符拼接
while recv_size < total_size:
res = client.recv(1024)
recv_data += res
recv_size += len(res)
print(recv_data.decode(‘gbk‘))
client.close()
FTP文件传输
所谓的FTP文件传输,就是输入一条get 1.txt命令,然后自动从服务器端下载了1.txt文件,代码和上面的比较相似
简单版本
说明一点:本操作在linux系统上操作,windows端的pycharm连接本地的Linux服务器,在服务器内执行操作,so请注意文件路径
# 服务端
import socket
import os
import json
import struct
# 设置路径,以后要写到配置文件中的
SHARE_DIR = r‘/py_study/day21-粘包/文件传输test/server/share‘
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind((‘127.0.0.1‘,10000))
server.listen(5)
while True:
conn,addr = server.accept()
print(‘客户端地址:‘,addr)
while True:
try:
# 1.收命令
obj = conn.recv(1024) # 收到b‘get 1.mp4‘
if not obj:break
# 2.解析命令,提取相应的命令参数
cmd = obj.decode(‘utf-8‘).split() # [‘get‘,‘1.mp4‘]
filename = cmd[1] # 文件名
# 3.以读的方式打开文件,读取内容发送给客户端
# 3.1制作固定长度的报头
header_dict = {
‘filename‘:filename,
‘md5‘:‘xxxxxxxx‘,
‘file_size‘:os.path.getsize(r‘%s/%s‘%(SHARE_DIR,filename))
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode(‘utf-8‘)
# 3.2发送报头的长度
conn.send(struct.pack(‘i‘,len(header_bytes)))
# 3.3再发送报头
conn.send(header_bytes)
# 4.发送真实的数据
with open(‘%s/%s‘%(SHARE_DIR,filename),‘rb‘) as f:
for line in f:
conn.send(line) # 逐行发送
except ConnectionResetError:
break
conn.close()
server.close()
# 客户端
import socket
import struct
import json
DOWNLOAD_DIR = r‘/py_study/day21-粘包/文件传输test/client/download‘
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect((‘127.0.0.1‘,10000))
while True:
# 1.发送命令
cmd = input(‘>>>‘).strip()
if not cmd:continue
client.send(cmd.encode(‘utf-8‘))
# 2.以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户端的新文件
# 2.1先接收报头的长度
obj = client.recv(4)
header_size = struct.unpack(‘i‘,obj)[0]
# 2.2再接收报头
header_bytes = client.recv(header_size)
# 3从报头中解析出对真实数据的描述信息
header_json = header_bytes.decode(‘utf-8‘)
header_dict = json.loads(header_json)
print(header_dict)
total_size = header_dict[‘file_size‘]
filename = header_dict[‘filename‘]
# 4.接收真实的数据
with open(‘%s/%s‘%(DOWNLOAD_DIR,filename),‘wb‘) as f:
recv_size = 0
while recv_size < total_size:
res = client.recv(1024)
f.write(res)
recv_size += len(res)
print(‘总大小:%s 已下载大小:%s‘%(total_size,recv_size))
client.close()
终极版本
# 服务端
import socket
import os
import json
import struct
# 设置路径,以后要写到配置文件中的
SHARE_DIR = r‘/py_study/day21-粘包/文件传输优化test/server/share‘
def get(cmd,conn):
filename = cmd[1] # 文件名
# 3.以读的方式打开文件,读取内容发送给客户端
# 3.1制作固定长度的报头
header_dict = {
‘filename‘: filename,
‘md5‘: ‘xxxxxxxx‘,
‘file_size‘: os.path.getsize(r‘%s/%s‘ % (SHARE_DIR, filename))
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode(‘utf-8‘)
# 3.2发送报头的长度
conn.send(struct.pack(‘i‘, len(header_bytes)))
# 3.3再发送报头
conn.send(header_bytes)
# 4.发送真实的数据
with open(‘%s/%s‘ % (SHARE_DIR, filename), ‘rb‘) as f:
for line in f:
conn.send(line)
def put():
pass
def run():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((‘127.0.0.1‘, 11115))
server.listen(5)
while True:
conn,addr = server.accept()
print(‘客户端地址:‘,addr)
while True:
try:
# 1.收命令
obj = conn.recv(1024) # 收到b‘get 1.mp4‘
print(‘收到数据‘)
if not obj:break
# 2.解析命令,提取相应的命令参数
cmds = obj.decode(‘utf-8‘).split() # [‘get‘,‘1.mp4‘]
if cmds[0] == ‘get‘:
get(cmds,conn)
elif cmds == ‘put‘:
pass
except ConnectionResetError:
break
conn.close()
server.close()
if __name__ == ‘__main__‘:
run()
# 客户端
import socket
import struct
import json
DOWNLOAD_DIR = r‘/py_study/day21-粘包/文件传输优化test/client/download‘
def get(client,cmds):
# 2.以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户端的新文件
# 2.1先接收报头的长度
print(‘进入get方法‘)
obj = client.recv(4)
print(‘发送数据成功‘)
header_size = struct.unpack(‘i‘,obj)[0]
print(‘解包完成‘)
# 2.2再接收报头
header_bytes = client.recv(header_size)
print(‘再次接收报头成功‘)
# 3从报头中解析出对真实数据的描述信息
header_json = header_bytes.decode(‘utf-8‘)
header_dict = json.loads(header_json)
print(header_dict)
total_size = header_dict[‘file_size‘]
filename = header_dict[‘filename‘]
# 4.接收真实的数据
with open(‘%s/%s‘%(DOWNLOAD_DIR,filename),‘wb‘) as f:
recv_size = 0
while recv_size < total_size:
res = client.recv(1024)
f.write(res)
recv_size += len(res)
print(‘总大小:%s 已下载大小:%s‘%(total_size,recv_size))
def put():
pass
def run():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((‘127.0.0.1‘,11115))
while True:
# 1.发送命令
cmd = input(‘>>>‘).strip()
if not cmd:continue
client.send(cmd.encode(‘utf-8‘))
cmds = cmd.split()
if cmds[0] == ‘get‘:
print(‘get方法‘)
get(client,cmds)
print(‘get方法执行完成‘)
elif cmds[0] == ‘put‘:
pass
client.close()
if __name__ == ‘__main__‘:
run()
以上是关于解决粘包问题的主要内容,如果未能解决你的问题,请参考以下文章