第二十八天- tcp下的粘包和解决方案
2018-11-26 07:59:54来源:博客园 阅读 ()
1.什么是粘包
写在前面:只有TCP有粘包现象,UDP永远不会粘包
1.TCP下的粘包
因为TCP协议是面向连接、面向流的,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,这就导致了数据量小的粘包现象;同时因为tcp的协议的安全可靠性,在没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会导致数据量大的粘包;
2.UDP没粘包原因:
UDP是无连接的,面向消息的,为提供高效率服务 ,并不会使用块的合并优化算法;同时由于UDP支持一对多的模式,所以接收端缓冲区采用了链式结构来记录每一个到达的UDP包,也就是在每个UDP包中有头(消息来源地址,端口等信息),这样,对于接收端来说就是有边界的,所以UDP永远没粘包。
2.两种粘包情况
1.情况一 发送方的缓存机制
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,优化机制会合到一起,产生粘包)
1 # 连续传小包被优化机制合并导致的粘包 2 3 import socket 4 5 server = socket.socket() 6 ip_port = ('127.0.0.1',8081) 7 server.bind(ip_port) 8 server.listen() 9 conn,addr = server.accept() 10 11 from_client_msg1 = conn.recv(1024).decode('utf-8') 12 from_client_msg2 = conn.recv(1024).decode('utf-8') 13 14 print(from_client_msg1) 15 print(from_client_msg2) 16 # heheheenenen 两条消息被优化合并在了一块 17 18 conn.close() 19 server.close()
1 import socket 2 3 client = socket.socket() 4 ip_port = ('127.0.0.1',8081) 5 client.connect(ip_port) 6 7 To_server_msg1 = client.send(b'hehehe') 8 To_server_msg2 = client.send(b'enenen') 9 10 client.close()
2.情况二 接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
1 # 传输数据超出接收范围导致的粘包 2 import socket 3 import subprocess 4 5 server = socket.socket() 6 ip_port = ('127.0.0.1',8001) 7 server.bind(ip_port) 8 server.listen() 9 conn,addr = server.accept() 10 11 while 1: 12 from_client_cmd = conn.recv(1024).decode('utf-8') 13 14 sub_obj = subprocess.Popen( 15 from_client_cmd, 16 shell=True, 17 stdout=subprocess.PIPE, 18 stderr=subprocess.PIPE, 19 ) 20 cmd_res = sub_obj.stdout.read() 21 print('结果长度>>>', len(cmd_res)) 22 conn.send(cmd_res) # 发内容给客户端
1 import socket 2 3 client = socket.socket() 4 ip_port = ('127.0.0.1',8001) 5 client.connect(ip_port) 6 7 8 while 1: 9 client_cmd = input('请输入系统指令>>>') 10 client.send(client_cmd.encode('utf-8')) 11 12 from_server_msg = client.recv(1024) # 接收返回的消息 13 14 print(from_server_msg.decode('gbk')) 15 # 可以看到没一次性接收完毕,执行下条命令出现了上次的结果,这就是数据大超出接收范围的粘包。
3.总结
黏包现象只发生在tcp协议中:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息间的界限,不知道一次性提取多少字节的数据所造成。
3.粘包解决方案
1.解决方案一:
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
1 import socket 2 import subprocess 3 4 server = socket.socket() 5 ip_port = ('127.0.0.1',8001) 6 server.bind(ip_port) 7 server.listen() 8 conn,addr = server.accept() 9 10 while 1: 11 from_client_cmd = conn.recv(1024).decode('utf-8') # 接收传来的命令 12 sub_obj = subprocess.Popen( 13 from_client_cmd, 14 shell=True, 15 stdout=subprocess.PIPE, 16 stderr=subprocess.PIPE, 17 ) 18 # subprocess对象.read 得到命令结果,是bytes类型的 19 str_byt = sub_obj.stdout.read() 20 str_len = len(str_byt) 21 print(str_len) 22 conn.send(str(str_len).encode('utf-8')) # 先发长度 23 24 from_client_msg = conn.recv(1024).decode('utf-8') 25 if from_client_msg == 'ok': 26 conn.send(str_byt) # 客户端确认收到长度后 再发送真实内容 27 else: 28 print("客户端未收到长度!") 29 break 30 31 conn.close() 32 server.close()
1 import socket 2 3 client = socket.socket() 4 ip_port = ('127.0.0.1',8001) 5 client.connect(ip_port) 6 7 8 while 1: 9 client_cmd = input('请输入系统指令>>>') 10 client.send(client_cmd.encode('utf-8')) 11 12 from_server_len = client.recv(1024).decode('utf-8') # 接收返回的长度 13 print(from_server_len) 14 client.send(b'ok') 15 16 from_server_msg = client.recv(int(from_server_len)) # 注意还原成int 17 print(from_server_msg.decode('gbk'))
不足之处:程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗
2.解决方案进阶:
可借助struct模块,这个模块可把要发的数据长度转成固定长度字节。这样客户端每次接收消息前只要先收到这个固定长度字节的内容,收到接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能完整接收的数据了。
struct模块:该模块可以把一个类型,如数字,转成固定长度的bytes
1 import struct 2 3 res = struct.pack('i',1111111) # i 模式 1111111 要转换的内容 4 print(res) # b'G\xf4\x10\x00'
模式,见下图:
1 import socket 2 import struct 3 import subprocess 4 5 server = socket.socket() 6 ip_port = ('127.0.0.1',8001) 7 server.bind(ip_port) 8 server.listen() 9 conn,addr = server.accept() 10 11 while 1: 12 from_client_cmd = conn.recv(1024).decode("utf-8") # 注意转码 13 # print(from_client_cmd) 14 sub_obj = subprocess.Popen( 15 from_client_cmd, 16 shell=True, 17 stdout=subprocess.PIPE, 18 stderr=subprocess.PIPE, 19 ) 20 cmd_res = sub_obj.stdout.read() # 得到bytes类型所有内容 21 str_len = len(cmd_res) 22 print(str_len) 23 24 str_len1 = struct.pack('i',str_len) # 把长度打包成4字节的bytes 25 26 conn.send(str_len1 + cmd_res) # 拼接字节 把长度和内容打包发给客户端
1 import socket, struct 2 3 client = socket.socket() 4 ip_port = ('127.0.0.1',8001) 5 client.connect(ip_port) 6 7 while 1: 8 str_cmd = input('请输入命令>>> ').encode('utf-8') 9 client.send(str_cmd) 10 # 先接收4个字节,4个字节是数据的真实长度转换而成的 11 str_len = client.recv(4) 12 # print(str_len) 13 num = struct.unpack('i',str_len)[0] # 注意解出来是一个元组 用下标把真实长度取出来 14 15 print(num) 16 17 str_res = client.recv(num) 18 print(str_res.decode('gbk'))
补充:获取缓冲区大小
1 # 获取socket缓冲区大小 2 import socket 3 from socket import SOL_SOCKET,SO_REUSEADDR,SO_SNDBUF,SO_RCVBUF 4 sk = socket.socket(type=socket.SOCK_DGRAM) 5 # sk.setsockopt(SOL_SOCKET,SO_RCVBUF,80*1024) 6 sk.bind(('127.0.0.1',8090)) 7 print('>>>>', (sk.getsockopt(SOL_SOCKET, SO_SNDBUF))/1024) 8 print('>>>>', sk.getsockopt(SOL_SOCKET, SO_RCVBUF))
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:python连接MySQL
- tcp服务端无线为多个客户端服务 2019-07-24
- python后端面试题 2019-07-24
- python 之网络编程(基于TCP协议Socket通信的粘包问题及解决 2019-07-24
- python 之 网络编程(基于TCP协议的套接字通信操作) 2019-07-24
- 一个基于tcp的socket简单对话小例子 2019-07-24
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash