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下的粘包和解决方案的更多相关文章

  1. 深入了解Netty【八】TCP拆包、粘包和解决方案

    1.TCP协议传输过程 TCP协议是面向流的协议,是流式的,没有业务上的分段,只会根据当前套接字缓冲区的情况进行拆包或者粘包: 发送端的字节流都会先传入缓冲区,再通过网络传入到接收端的缓冲区中,最终由 ...

  2. python中TCP粘包问题解决方案

    TCP协议中的粘包问题 1.粘包现象 基于TCP写一个远程cmd功能 #服务端 import socket import subprocess sever = socket.socket() seve ...

  3. 基于tcp协议的粘包问题(subprocess、struct)

    要点: 报头  固定长度bytes类型 1.粘包现象 粘包就是在获取数据时,出现数据的内容不是本应该接收的数据,如:对方第一次发送hello,第二次发送world,我放接收时,应该收两次,一次是hel ...

  4. 网络编程之tcp协议以及粘包问题

    网络编程tcp协议与socket以及单例的补充 一.单例补充 实现单列的几种方式 #方式一:classmethod # class Singleton: # # __instance = None # ...

  5. TCP通讯处理粘包详解

    TCP通讯处理粘包详解 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的 ...

  6. Netty(三) 什么是 TCP 拆、粘包?如何解决?

    前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...

  7. 什么是 TCP 拆、粘包?如何解决(Netty)

    前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...

  8. 【Python】TCP Socket的粘包和分包的处理

    Reference: http://blog.csdn.net/yannanxiu/article/details/52096465 概述 在进行TCP Socket开发时,都需要处理数据包粘包和分包 ...

  9. Netty系列(四)TCP拆包和粘包

    Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...

随机推荐

  1. odoo开发笔记 -- odoo仪表板集成hightcharts

    highcharts图表插件初探 http://www.cnblogs.com/liubei/p/highchartsOption.html

  2. Linux命令对应的英文全称

    su:Swith user  切换用户,切换到root用户cat: Concatenate  串联uname: Unix name  系统名称df: Disk free  空余硬盘du: Disk u ...

  3. Python多线程-Barrier(障碍对象)

    Barrier(parties, action=None, timeout=None) 每个线程通过调用wait()尝试通过障碍,并阻塞,直到阻塞的数量达到parties时,阻塞的线程被同时全部释放. ...

  4. (转)Python Mixins 机制

    原文:https://github.com/dengshuan/notes/blob/master/techs/python-mixins.org https://blog.csdn.net/u012 ...

  5. 用Elasticsearch做大规模数据的多字段、多类型索引检索

    本文同时发布在我的个人博客 之前尝试了用mysql做大规模数据的检索优化,可以看到单字段检索的情况下,是可以通过各种手段做到各种类型索引快速检索的,那是一种相对简单的场景. 但是实际应用往往会复杂一些 ...

  6. mysql 导出 sql的执行结果到 csv文件

    需求: 1. 执行某 SQL 的结果: 2. 将结果导出到 csv文件: 3. 通过命令行执行: mysql ;" | sed 's/\t/","/g;s/^/" ...

  7. 常见hash原理

    原文出处:http://www.itmian4.com/forum.php?mod=viewthread&tid=4736&fromuid=1 散列表,它是基于快速存取的角度设计的,也 ...

  8. 复刻smartbits的国产网络测试工具minismb-操作技巧

    复刻smartbits的国产网络性能测试工具smartbits,是一款专门用于测试智能路由器,网络交换机的性能和稳定性的软硬件相结合的工具.可以通过此工具测试任何ip网络设备的端口吞吐率,带宽,并发连 ...

  9. Tomcat学习总结(4)——基于Tomcat7、Java、WebSocket的服务器推送聊天室

    前言           HTML5 WebSocket实现了服务器与浏览器的双向通讯,双向通讯使服务器消息推送开发更加简单,最常见的就是即时通讯和对信息实时性要求比较高的应用.以前的服务器消息推送大 ...

  10. 偏流角为什么是arcsin(w/V)

    偏流角为什么是arcsin(w/V) 2015-10-22 风螺旋线   回答这个问题要从速度三角形说起(需要了解一点三角函数,但很基础,不用担心). 传统的速度三角形如下图所示: (背一段书) DA ...