博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
网络编程-粘包问题以及解决方案
阅读量:7003 次
发布时间:2019-06-27

本文共 15787 字,大约阅读时间需要 52 分钟。

粘包问题以及解决方案

# 粘包以及解决方法:# 粘包的概念# 所谓粘包问题是因为在流传输中,接收方一次接收数据,因为不知道消息之间的界限,# 不知道一次性提取多少字节的数据而造成的不能体现一个完整的消息数据的现象# 粘包产生的场景:# 双方发送一段数据,有且只有一段数据,就关闭连接,这样就不会出现粘包问题# 如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包# 如果双方建立连接,需要在连接后一段时间内发送不同结构数据,就可能粘包# 粘包产生的原因:# 在tcp流传输中出现,以下从发送和接收两方面来看造成粘包的原因。UDP不会出现粘包,因为它有消息边界# 1 发送端需要等缓冲区满才发送出去,造成粘包。就是这段数据不够塞满缓存,用下段数据塞满了,丧失了该段数据的完整性# 2 接收方不及时接收缓冲区的包,造成多个包接收。就是接收了一个包,又接收了几个包,搞到一块了,也丧失了一段数据的完整性# 粘包的后果:# 丧失了该段数据的完整性,那段数据变多了,或者变少了,变得不完整了# 如何防止粘包:# 所以我们的任务是保证传输的数据就是那一段==># 完整性就是数据的开始,结束,数据的长度==># 所以你想到了统计传输数据字节数,按照字节数传输完整# 其实,只要传输的个数小于等于接收函数conn.recv(1024)内的参数,都是不会产生粘包的,但是超过了肯定粘包import struct#i 是4字节data_size=1008res=struct.pack("i",data_size)print(res)print(len(res))# b'\xf0\x03\x00\x00'# 4res1=struct.unpack("i",res)print(res1)res2=struct.unpack("i",res)[0]print(res2)# (1008,)# 1008head_dic={"data_size":1688,"filename":"a.txt","hash":None}

 

struct.pack这个函数的参数是无限的第一个参数是定义打包的格式第二个参数开始,所有参数都是要打包的内容~而第一个格式参数的具体写法参见下表:Format     c Type     Python     Notex     pad byte     no valuec     char     string of length 1b     signedchar     integerB     unsignedchar     integer?     _Bool     bool     (1)h     short     integerH     unsignedshort     integeri     int     integerI     unsignedint     integer or longl     long     integerL     unsignedlong     longq     longlong     long     (2)Q     unsignedlonglong     long     (2)f     float     floatd     double     floats     char[]     stringp     char[]     stringP     void*     long还有相应的大/小端的问题:@     native     native=     native     standard<     little-endian     standard>     big-endian     standard!     network (= big-endian)     standard大/小端标记可以省略,貌似默认是小端你的例子中,L表示无符号的长整形值所以按你的写法打包出来的就应该是一个小端的无符号长整型数据

 

粘包客户端和服务端:

1 # 粘包案例client.py 2 import socket,time 3 import subprocess 4 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5  6 ip_port=('127.0.0.1',8080) 7 phone.connect(ip_port) 8  9 #10 phone.send('helloworld'.encode('utf-8'))11 time.sleep(3)12 phone.send('i am ada'.encode('utf-8'))
客户端
1 # 粘包案例server.py 2 import socket,time 3 import subprocess 4 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5  6 ip_port=('127.0.0.1',8080) 7 phone.bind(ip_port) 8 phone.listen(5) 9 conn,addr=phone.accept()10 11 #接收参数都大于客户端的字节数,不会粘包12 # data1=conn.recv(1024)13 # data2=conn.recv(1024)14 15 data1=conn.recv(5) #b'h'16 time.sleep(5)17 data2=conn.recv(1024) #b'elloworldSB'18 19 20 print('第一个包',data1)21 print('第二个包',data2)
服务端

 

实现粘包方案的客户端和服务端

1 #client.py 2 import socket 3 import struct 4 import json 5 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 6 # 拨通电话 7 # ip_port = ('127.0.0.1', 8080) 8 ip_port = ('192.168.16.114', 8081) 9 phone.connect(ip_port)10 # 通信循环11 while True:12     # 发消息13     cmd = input('>>: ').strip()14     if not cmd: continue15     phone.send(bytes(cmd, encoding='utf-8'))16 17     # part1:先收报头的长度18     head_struct=phone.recv(4)19     head_len=struct.unpack('i',head_struct)[0]20 21     # part2:再收报头22     head_bytes=phone.recv(head_len)23     head_json=head_bytes.decode('utf-8')24 25     head_dic=json.loads(head_json)26     print(head_dic)27     data_size = head_dic['data_size']28 29     #part3:收数据30     recv_size = 031     recv_data = b''32     while recv_size < data_size:33         data = phone.recv(1024)34         recv_size += len(data)35         recv_data += data36 37     print(recv_data.decode('utf-8'))38 phone.close()
客户端
1 #coding:utf-8 2 # server.py 3 # 这里的思路是: 4 # 服务端,将长度等未来需要的参数都送进字典结构,并json字符串化,编码为字节传送过去 5 # 客户端,将接收到的先把字节解码,再反序列化,转换成字典结构,取出变量 6  7 #买手机 8 import socket 9 import struct10 import json11 import subprocess12 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)13 #绑定电话卡14 ip_port=('192.168.16.114',8081)15 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)16 phone.bind(ip_port)17 #开机18 phone.listen(5)19 #等待电话20 21 #链接循环22 while True:23     conn,addr=phone.accept()24     print('client addr',addr)25     #通讯循环26     while True:27         try:28             cmd=conn.recv(1024)29             res=subprocess.Popen(cmd.decode('utf-8'),30                              shell=True,31                              stdout=subprocess.PIPE,32                              stderr=subprocess.PIPE)33             out_res=res.stdout.read()34             err_res=res.stderr.read()35             data_size=len(out_res)+len(err_res)36             head_dic={
'data_size':data_size}37 head_json=json.dumps(head_dic)38 head_bytes=head_json.encode('utf-8')39 40 #part1:先发报头的长度41 head_len=len(head_bytes)42 conn.send(struct.pack('i',head_len))43 #part2:再发送报头44 conn.send(head_bytes)45 #part3:最后发送数据部分46 conn.send(out_res)47 conn.send(err_res)48 49 except Exception:50 break51 52 conn.close()53 phone.close()
服务端

 

简单的cmd输入客户端和服务端:

1 # 这个案例是简单的命令行输入实现,cmd_client.py 2 import socket 3 import struct 4 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5  6  7 ip_port = ('127.0.0.1', 8081) 8 phone.connect(ip_port) 9 # 通信循环10 while True:11     # 发消息12     cmd = input('>>: ').strip()13     if not cmd: continue14     phone.send(bytes(cmd, encoding='utf-8'))15 16     #收报头17     baotou=phone.recv(4)18     data_size=struct.unpack('i',baotou)[0]19 20     # 收数据21     recv_size=022     recv_data=b''23     while recv_size < data_size:24         data=phone.recv(1024)  #接收0-1024字节之间任意个数并返回,不是只返回1024个25         recv_size+=len(data)26         recv_data+=data27 28     print(recv_data.decode('utf-8'))29 phone.close()
客户端
1 #coding:utf-8 2 # 这个案例是简单的命令行输入实现,cmd_server.py 3 # 解决思路:利用struct.pack('i',data_size))封装数据长度并字节化作为报头传给客户端,所以先传报头,再传数据 4 # 在客户端通过struct.unpack('i',baotou)[0],解压字节变成正常数字来使用,所以先接报头,再接数据 5  6  7 import socket 8 import struct 9 import subprocess10 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)11 #绑定电话卡12 ip_port=('127.0.0.1',8081)13 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)14 phone.bind(ip_port)15 #开机16 phone.listen(5)17 #等待电话18 19 #链接循环20 while True:21     conn,addr=phone.accept()22     print('client addr',addr)23     #通讯循环24     while True:25         try:26             cmd=conn.recv(1024)27             res=subprocess.Popen(cmd.decode('utf-8'),28                              shell=True,29                              stdout=subprocess.PIPE,30                              stderr=subprocess.PIPE)31             out_res=res.stdout.read()32             err_res=res.stderr.read()33             data_size=len(out_res)+len(err_res)34             #发送报头35             conn.send(struct.pack('i',data_size))36             #发送数据部分37             conn.send(out_res)38             conn.send(err_res)39 40         except Exception:41             break42 43     conn.close()44 phone.close()
服务端

 

简单上传下载客户端和服务端:

1 # upload and download       client.py  2   3 import socket  4 import struct  5 import json  6 import os  7   8   9  10 class MYTCPClient: 11     address_family = socket.AF_INET 12  13     socket_type = socket.SOCK_STREAM 14  15     allow_reuse_address = True 16  17     max_packet_size = 8192 18  19     coding='utf-8' 20  21     request_queue_size = 5 22  23     def __init__(self, server_address, connect=True): 24         self.server_address=server_address 25         self.socket = socket.socket(self.address_family, 26                                     self.socket_type) 27         if connect: 28             try: 29                 # 连接服务器 30                 self.client_connect() 31             except: 32                 # 关闭服务器 33                 self.client_close() 34                 raise 35  36     def client_connect(self): 37         self.socket.connect(self.server_address) 38  39     def client_close(self): 40         self.socket.close() 41  42     def run(self): 43  44         while True: 45             inp=input(">>: ").strip() 46             # 如果字符串为空,继续循环 47             if not inp:continue 48  49             print("inp:") 50             # put / users / alex / desktop / file_upload / hello.mp4 51  52             l=inp.split() 53             print("inp.split():",l) 54             # ['put', '/users/alex/desktop/file_upload/hello.mp4'] 55  56             cmd=l[0] 57             print("l=inp.split() de l[0]:",l[0]) 58             # l = inp.split() de l[0]: put 59             # 反射反省 60  61             # 类对象是否有cmd这个东西,cmd拿到的应该是命令关键字put 62             # 这个类有没有put 这个函数属性 63             if hasattr(self,cmd): 64                 print("hasattr(self,cmd):",hasattr(self,cmd)) 65                 # hasattr(self, cmd): True 66  67                 # 获得这个函数属性东西 68                 func=getattr(self,cmd) 69                 print("getattr(self,cmd):", getattr(self, cmd)) 70                 # getattr(self, cmd): < bound method MYTCPClient.put of 71                 # < __main__.MYTCPClient object at 0x104d12240 >> 72  73                 # 执行这个函数东西 74                 func(l) 75                 print("func(l):", func(l)) 76                 # none 77  78     # 上传 79     def put(self,args): 80  81         # 命令行应该是put + path 82         # 取出命令各元素 83         cmd=args[0] 84  85         filename=args[1] 86         print("run cmd/filename:",cmd,filename) 87         # run cmd/filename :put /users/alex/desktop/file_upload/hello.mp4 88  89         if not os.path.isfile(filename): 90             # 如果文件不存在,返回 91             print('file:%s is not exists' %filename) 92             return 93         else: 94             # 如果文件存在,获取文件大小,此时获取文件大小 95             filesize=os.path.getsize(filename) 96  97         # 此时文件信息包涵:命令+包涵文件名的文件路径+大小 98         head_dic={
'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize} 99 print("head_dic:",head_dic)100 # head_dic: {'cmd': 'put', 'filesize': 24597595, 'filename': 'hello.mp4'}101 102 # 字符串序列化103 head_json=json.dumps(head_dic)104 # 字节类型105 head_json_bytes=bytes(head_json,encoding=self.coding)106 107 # 打包,i代表int 数据108 head_struct=struct.pack('i',len(head_json_bytes))109 110 # 发送长度111 self.socket.send(head_struct)112 113 # 发送数据114 self.socket.send(head_json_bytes)115 116 # 统计目前发送的数据量117 send_size=0118 with open(filename,'rb') as f:119 for line in f:120 self.socket.send(line)121 send_size+=len(line)122 print(send_size)123 else:124 # 发送完毕打印上传成功125 print('upload successful')126 127 128 client=MYTCPClient(('127.0.0.1',9003))129 130 client.run()
客户端
1 # upload and download       server.py  2   3 import socket  4   5 # 传递字符串时,不必担心太多的问题,而当传递诸如int、char之类的基本数据的时候,  6 # 就需要有一种机制将某些特定的结构体类型打包成二进制流的字符串然后再网络传输,  7 # 而接收端也应该可以通过某种机制进行解包还原出原始的结构体数据  8 import struct  9 import json 10 import subprocess 11 import os 12  13  14 # ftp服务器类,为什么要用类 15 class MYTCPServer: 16  17     # address即使用IP 18     address_family = socket.AF_INET 19  20     # 基于tcp流套接字 21     socket_type = socket.SOCK_STREAM 22  23     # 允许重用地址 24     allow_reuse_address = True 25  26     # 最大包大小1024*8=8192 27     max_packet_size = 8192 28  29     coding='utf-8' 30  31     # 请求队列量 32     request_queue_size = 5 33  34     #上传文件地址,同时也应该是下载地址,应该可以自定义 35     server_dir='/Users/Alex/desktop/file_upload' 36  37     # 初始化函数 38     def __init__(self, server_address, bind_and_activate=True): 39         """Constructor.  May be extended, do not override. 40         :param:绑定地址(ip+port),是否绑定激活(默认绑定),便于自定义地址 41         """ 42         self.server_address=server_address 43  44         # 将全局变量拿过来 45         self.socket = socket.socket(self.address_family, 46                                     self.socket_type) 47  48         # 是否绑定,默认绑定 49         if bind_and_activate: 50             try: 51                 # 如果绑定,就绑定 52                 self.server_bind() 53  54                 # 就激活监听 55                 self.server_activate() 56             except: 57                 self.server_close() 58                 raise 59  60     def server_bind(self): 61         """Called by constructor to bind the socket. 62         """ 63         # 如果允许重用 64         if self.allow_reuse_address: 65             # 设置重用 66             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 67  68         # 直接执行绑定传过来的地址端口 69         self.socket.bind(self.server_address) 70  71         # gethostname()返回运行程序所在的计算机的主机名: 72         self.server_address = self.socket.getsockname() 73         print("self.socket.getsockname():",self.socket.getsockname()) 74         # self.socket.getsockname(): ('127.0.0.1', 9002) 75         # bogon/localhost 76  77     def server_activate(self): 78         """Called by constructor to activate the server. 79         """ 80         # 监听 81         self.socket.listen(self.request_queue_size) 82  83     def server_close(self): 84         """Called to clean-up the server. 85         """ 86         # 关闭服务器 87         self.socket.close() 88  89     # 封装接收conn,data的请求,获取请求 90     def get_request(self): 91         """Get the request and client address from the socket. 92         """ 93         # 返回accept 94         return self.socket.accept() 95  96     # 关闭请求 97     def close_request(self, request): 98         """Called to clean up an individual request. 99             调去清理关闭个人请求100         """101         request.close()102 103     # 初始化后进行主函数104     def run(self):105 106         # 套接字循环107         while True:108 109             self.conn,self.client_addr=self.get_request()110             print('from client 用户数据: ',self.client_addr)111             # from client 用户数据:  ('127.0.0.1', 59479)112 113             # 通信循环114             while True:115                 try:116                     # 参数4的特殊意义:117                     head_struct = self.conn.recv(4)118                     if not head_struct:break119 120                     # 解包121                     print("self.conn.recv(4):",head_struct)122                     # self.conn.recv(4): b'=\x00\x00\x00'123 124                     # 获取文件大小125                     head_len = struct.unpack('i', head_struct)[0]126                     print("head_len:",head_len)127                     # head_len: 61128 129                     # 根据文件大小接收数据130                     head_json = self.conn.recv(head_len).decode(self.coding)131 132                     # 反序列化取出字典133                     head_dic = json.loads(head_json)134 135                     print(head_dic)136                     # {'cmd': 'put', 'filesize': 24597595, 'filename': 'hello.mp4'}137                     #head_dic={'cmd':'put','filename':'a.txt','filesize':123123}138                     cmd=head_dic['cmd']139 140                     # 执行put函数141                     if hasattr(self,cmd):142                         func=getattr(self,cmd)143                         func(head_dic)144                 except Exception:145                     break146 147     def put(self,args):148         # 将服务器文件上传路径与上传文件名拼接并正常化路径149         file_path=os.path.normpath(os.path.join(150             self.server_dir,151             args['filename']152         ))153 154         # 文件大小155         filesize=args['filesize']156 157 158         print('----->', file_path)159         # / Users /Alex/desktop/file_upload/hello.mp4160 161         # 接收初始化量,判断接收的多少162         recv_size=0163 164         # 根据路径打开服务器文件区域,根据要求新建文件,写入165         with open(file_path,'wb') as f:166             while recv_size < filesize:167 168                 recv_data=self.conn.recv(self.max_packet_size)169                 f.write(recv_data)170                 recv_size+=len(recv_data)171                 # 查看接收的多少,打印进度条172                 print('recvsize:%s filesize:%s' %(recv_size,filesize))173 174 175 176 177 178 179 tcpserver1=MYTCPServer(('127.0.0.1',9003))180 181 tcpserver1.run()
服务端

 

  

 

转载于:https://www.cnblogs.com/adamans/articles/7562227.html

你可能感兴趣的文章
深入分析事务的隔离级别
查看>>
基于Vue2 搭建移动端 webapp 框架
查看>>
Android View体系(四)从源码解析Scroller
查看>>
Cannot lock storage /tmp/hadoop-root/dfs/name. The directory is already locked.
查看>>
BFS和DFS的java实现
查看>>
Struts2框架起源
查看>>
Chromosome coordinate systems: 0-based, 1-based
查看>>
Myeclipse10集成Flex4.6
查看>>
java电影站点开发经验3
查看>>
Android 打造随意层级树形控件 考验你的数据结构和设计
查看>>
文本域textarea
查看>>
利用泛型和反射,管理配置文件,把Model转换成数据行,并把数据行转换成Model
查看>>
leetcode 235: Lowest Common Ancestor of a Binary Search Tree
查看>>
java nio最白话理解
查看>>
OpenCV学习代码记录——人脸检测
查看>>
springboot的日志框架slf4j (使用logback输出日志以及使用)
查看>>
vim显示行号、语法高亮、自动缩进、添加下划线的设置
查看>>
iis7下站点日志默认位置
查看>>
canvas 画线
查看>>
gif软件(ShareX)
查看>>