
解决粘包问题
发布日期:2021-05-14 21:58:28
浏览次数:14
分类:博客文章
本文共 9578 字,大约阅读时间需要 31 分钟。
解决粘包问题
在OSI七层模型中,我们可以看到,数据包从应用层产生,会在应用层生成一个头文件+数据的包传递给下一层,在下一层中它会认识这个包就是一个整体,然后会在这上面再重新添加一个包,直到物理层发送电信号,到了服务端一层层的解包,()
那么如果我们想解决粘包的问题,首先最重要的就是要知道这个数据包到底有多大,那么应该怎么知道呢?
struct模块
struct模块是Python中内置模块,它用来在C语言中的结构体与Python中的字符串之间进行转换,数据一般来自网络或文件
常用方法
struct.pack(fmt,v1)返回的是一个字符串,是参数按照fmt数据格式组合而成struct.unpack(fmt.string)按照给定数据格式解开(通常都是由struct.pack进行打包)数据,返回值是一个tuple
格式符
具体请参考:()
我们目前能用到的就是fmt=i,固定返回的是4个bytes,那么我们就可以和OSI七层模型一样,在还没有发送自己的真实数据之前,先告诉客户端我会发送多大的数据,也就是先发一个头部文件过去,然后客户端就开始进入循环,一直把这个数据接收完才可以,看以下代码
简单版本
# 服务端# 导入模块import socketimport subprocessimport 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 socketimport structclient = 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: # 如果接收的大小
终极版本
# 服务端# 导入模块import socketimport subprocessimport jsonimport 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 socketimport structimport 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 socketimport osimport jsonimport 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 socketimport structimport jsonDOWNLOAD_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 socketimport osimport jsonimport 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(): passdef 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 socketimport structimport jsonDOWNLOAD_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(): passdef 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()
发表评论
最新留言
不错!
[***.144.177.141]2025年04月26日 19时31分49秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
解决Nginx 404 not found问题
2019-03-09
广东外语外贸大学第三届网络安全大赛Writeup
2019-03-09
VS中 fatal error LNK1123: 转换到 COFF 期间失败 的解决方法
2019-03-09
ant design pro v5去掉右边content区域的水印
2019-03-09
JavaScript——使用iterator遍历迭代map,set集合元素
2019-03-09
Course Schedule II
2019-03-10
C#中文转换成拼音
2019-03-10
C++错误笔记
2019-03-10
SpringBoot使用RedisTemplate简单操作Redis的五种数据类型
2019-03-10
qt中转到槽后如何取消信号与槽关联
2019-03-10
qt问题记录-spin box与double spin box
2019-03-10
移动端事件
2019-03-10
spring-day01
2019-03-10
抖音发布黄金时间段,抖音上热门最佳时间
2019-03-10
我的图床~
2019-03-10
Thymeleaf sec:authorize 标签不生效
2019-03-11
Iterable与Iterator
2019-03-11
关于WebView当前地址问题的疑惑
2019-03-11
Python机器学习(九十二)Pandas 统计
2019-03-11