第二十八天- tcp下的粘包和解决方案
1.什么是粘包
写在前面:只有TCP有粘包现象,UDP永远不会粘包
1.TCP下的粘包
因为TCP协议是面向连接、面向流的,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,这就导致了数据量小的粘包现象;同时因为tcp的协议的安全可靠性,在没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会导致数据量大的粘包;

2.UDP没粘包原因:
UDP是无连接的,面向消息的,为提供高效率服务 ,并不会使用块的合并优化算法;同时由于UDP支持一对多的模式,所以接收端缓冲区采用了链式结构来记录每一个到达的UDP包,也就是在每个UDP包中有头(消息来源地址,端口等信息),这样,对于接收端来说就是有边界的,所以UDP永远没粘包。
2.两种粘包情况
1.情况一 发送方的缓存机制
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,优化机制会合到一起,产生粘包)
# 连续传小包被优化机制合并导致的粘包 import socket server = socket.socket()
ip_port = ('127.0.0.1',8081)
server.bind(ip_port)
server.listen()
conn,addr = server.accept() from_client_msg1 = conn.recv(1024).decode('utf-8')
from_client_msg2 = conn.recv(1024).decode('utf-8') print(from_client_msg1)
print(from_client_msg2)
# heheheenenen 两条消息被优化合并在了一块 conn.close()
server.close()
View 小数据粘包_server Code
import socket client = socket.socket()
ip_port = ('127.0.0.1',8081)
client.connect(ip_port) To_server_msg1 = client.send(b'hehehe')
To_server_msg2 = client.send(b'enenen') client.close()
View 小数据粘包_client Code
2.情况二 接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
# 传输数据超出接收范围导致的粘包
import socket
import subprocess server = socket.socket()
ip_port = ('127.0.0.1',8001)
server.bind(ip_port)
server.listen()
conn,addr = server.accept() while 1:
from_client_cmd = conn.recv(1024).decode('utf-8') sub_obj = subprocess.Popen(
from_client_cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
cmd_res = sub_obj.stdout.read()
print('结果长度>>>', len(cmd_res))
conn.send(cmd_res) # 发内容给客户端
数据大粘包_server
import socket client = socket.socket()
ip_port = ('127.0.0.1',8001)
client.connect(ip_port) while 1:
client_cmd = input('请输入系统指令>>>')
client.send(client_cmd.encode('utf-8')) from_server_msg = client.recv(1024) # 接收返回的消息 print(from_server_msg.decode('gbk'))
# 可以看到没一次性接收完毕,执行下条命令出现了上次的结果,这就是数据大超出接收范围的粘包。
数据大粘包_client
3.总结
黏包现象只发生在tcp协议中:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息间的界限,不知道一次性提取多少字节的数据所造成。
3.粘包解决方案
1.解决方案一:
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

import socket
import subprocess server = socket.socket()
ip_port = ('127.0.0.1',8001)
server.bind(ip_port)
server.listen()
conn,addr = server.accept() while 1:
from_client_cmd = conn.recv(1024).decode('utf-8') # 接收传来的命令
sub_obj = subprocess.Popen(
from_client_cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
# subprocess对象.read 得到命令结果,是bytes类型的
str_byt = sub_obj.stdout.read()
str_len = len(str_byt)
print(str_len)
conn.send(str(str_len).encode('utf-8')) # 先发长度 from_client_msg = conn.recv(1024).decode('utf-8')
if from_client_msg == 'ok':
conn.send(str_byt) # 客户端确认收到长度后 再发送真实内容
else:
print("客户端未收到长度!")
break conn.close()
server.close()
解决小包粘包_服务端
import socket client = socket.socket()
ip_port = ('127.0.0.1',8001)
client.connect(ip_port) while 1:
client_cmd = input('请输入系统指令>>>')
client.send(client_cmd.encode('utf-8')) from_server_len = client.recv(1024).decode('utf-8') # 接收返回的长度
print(from_server_len)
client.send(b'ok') from_server_msg = client.recv(int(from_server_len)) # 注意还原成int
print(from_server_msg.decode('gbk'))
解决小包粘包_客户端
不足之处:程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗
2.解决方案进阶:
可借助struct模块,这个模块可把要发的数据长度转成固定长度字节。这样客户端每次接收消息前只要先收到这个固定长度字节的内容,收到接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能完整接收的数据了。
struct模块:该模块可以把一个类型,如数字,转成固定长度的bytes
import struct
res = struct.pack('i',1111111) # i 模式 1111111 要转换的内容
print(res) # b'G\xf4\x10\x00'
模式,见下图:

import socket
import struct
import subprocess server = socket.socket()
ip_port = ('127.0.0.1',8001)
server.bind(ip_port)
server.listen()
conn,addr = server.accept() while 1:
from_client_cmd = conn.recv(1024).decode("utf-8") # 注意转码
# print(from_client_cmd)
sub_obj = subprocess.Popen(
from_client_cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
cmd_res = sub_obj.stdout.read() # 得到bytes类型所有内容
str_len = len(cmd_res)
print(str_len) str_len1 = struct.pack('i',str_len) # 把长度打包成4字节的bytes conn.send(str_len1 + cmd_res) # 拼接字节 把长度和内容打包发给客户端
struct方案解决粘包_server
import socket, struct client = socket.socket()
ip_port = ('127.0.0.1',8001)
client.connect(ip_port) while 1:
str_cmd = input('请输入命令>>> ').encode('utf-8')
client.send(str_cmd)
# 先接收4个字节,4个字节是数据的真实长度转换而成的
str_len = client.recv(4)
# print(str_len)
num = struct.unpack('i',str_len)[0] # 注意解出来是一个元组 用下标把真实长度取出来 print(num) str_res = client.recv(num)
print(str_res.decode('gbk'))
struct方案解决粘包_client
补充:获取缓冲区大小
# 获取socket缓冲区大小
import socket
from socket import SOL_SOCKET,SO_REUSEADDR,SO_SNDBUF,SO_RCVBUF
sk = socket.socket(type=socket.SOCK_DGRAM)
# sk.setsockopt(SOL_SOCKET,SO_RCVBUF,80*1024)
sk.bind(('127.0.0.1',8090))
print('>>>>', (sk.getsockopt(SOL_SOCKET, SO_SNDBUF))/1024)
print('>>>>', sk.getsockopt(SOL_SOCKET, SO_RCVBUF))
第二十八天- tcp下的粘包和解决方案的更多相关文章
- 深入了解Netty【八】TCP拆包、粘包和解决方案
1.TCP协议传输过程 TCP协议是面向流的协议,是流式的,没有业务上的分段,只会根据当前套接字缓冲区的情况进行拆包或者粘包: 发送端的字节流都会先传入缓冲区,再通过网络传入到接收端的缓冲区中,最终由 ...
- python中TCP粘包问题解决方案
TCP协议中的粘包问题 1.粘包现象 基于TCP写一个远程cmd功能 #服务端 import socket import subprocess sever = socket.socket() seve ...
- 基于tcp协议的粘包问题(subprocess、struct)
要点: 报头 固定长度bytes类型 1.粘包现象 粘包就是在获取数据时,出现数据的内容不是本应该接收的数据,如:对方第一次发送hello,第二次发送world,我放接收时,应该收两次,一次是hel ...
- 网络编程之tcp协议以及粘包问题
网络编程tcp协议与socket以及单例的补充 一.单例补充 实现单列的几种方式 #方式一:classmethod # class Singleton: # # __instance = None # ...
- TCP通讯处理粘包详解
TCP通讯处理粘包详解 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的 ...
- Netty(三) 什么是 TCP 拆、粘包?如何解决?
前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...
- 什么是 TCP 拆、粘包?如何解决(Netty)
前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...
- 【Python】TCP Socket的粘包和分包的处理
Reference: http://blog.csdn.net/yannanxiu/article/details/52096465 概述 在进行TCP Socket开发时,都需要处理数据包粘包和分包 ...
- Netty系列(四)TCP拆包和粘包
Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...
随机推荐
- odoo开发笔记 -- odoo仪表板集成hightcharts
highcharts图表插件初探 http://www.cnblogs.com/liubei/p/highchartsOption.html
- Linux命令对应的英文全称
su:Swith user 切换用户,切换到root用户cat: Concatenate 串联uname: Unix name 系统名称df: Disk free 空余硬盘du: Disk u ...
- Python多线程-Barrier(障碍对象)
Barrier(parties, action=None, timeout=None) 每个线程通过调用wait()尝试通过障碍,并阻塞,直到阻塞的数量达到parties时,阻塞的线程被同时全部释放. ...
- (转)Python Mixins 机制
原文:https://github.com/dengshuan/notes/blob/master/techs/python-mixins.org https://blog.csdn.net/u012 ...
- 用Elasticsearch做大规模数据的多字段、多类型索引检索
本文同时发布在我的个人博客 之前尝试了用mysql做大规模数据的检索优化,可以看到单字段检索的情况下,是可以通过各种手段做到各种类型索引快速检索的,那是一种相对简单的场景. 但是实际应用往往会复杂一些 ...
- mysql 导出 sql的执行结果到 csv文件
需求: 1. 执行某 SQL 的结果: 2. 将结果导出到 csv文件: 3. 通过命令行执行: mysql ;" | sed 's/\t/","/g;s/^/" ...
- 常见hash原理
原文出处:http://www.itmian4.com/forum.php?mod=viewthread&tid=4736&fromuid=1 散列表,它是基于快速存取的角度设计的,也 ...
- 复刻smartbits的国产网络测试工具minismb-操作技巧
复刻smartbits的国产网络性能测试工具smartbits,是一款专门用于测试智能路由器,网络交换机的性能和稳定性的软硬件相结合的工具.可以通过此工具测试任何ip网络设备的端口吞吐率,带宽,并发连 ...
- Tomcat学习总结(4)——基于Tomcat7、Java、WebSocket的服务器推送聊天室
前言 HTML5 WebSocket实现了服务器与浏览器的双向通讯,双向通讯使服务器消息推送开发更加简单,最常见的就是即时通讯和对信息实时性要求比较高的应用.以前的服务器消息推送大 ...
- 偏流角为什么是arcsin(w/V)
偏流角为什么是arcsin(w/V) 2015-10-22 风螺旋线 回答这个问题要从速度三角形说起(需要了解一点三角函数,但很基础,不用担心). 传统的速度三角形如下图所示: (背一段书) DA ...