粘包现象与解决粘包问题

article/2025/10/13 16:53:59

粘包现象与解决粘包问题

一、引入

粘包问题主要出现在用TCP协议传输中才会出现的问题,UDP不会出现,因为TCP传输中他会服务端会一次性把所有东西一并丢入缓存区,而读取的内容大小有时候没法准确的做到一一读取,所有会存在粘包。

UDP他传输的时候是吧一个个内容丢过去,不管客户端能否完全接受到内容他都会接受他制定大小的内容,而内容大于他接受设定的大小时候多余的东西会被丢到

注意:只有TCP才会出现粘包现象,UDP永远不会出现粘包现象

一、粘包现象介绍

1.socket收发消息的原理

其实我们发送数据并不是直接发送给对方, 而是应用程序将数据发送到本机操作系统的缓存里边, 当数据量小, 发送的时间间隔短, 操作系统就会在缓存区先攒够一个TCP段再通过网卡一起发送, 接收数据也是一样的, 先在操作系统的缓存存着, 然后应用程序再从操作系统中取出数据

image-20210118142809055

1.1缓冲区的作用:存储少量数据

  • 如果你的网络出现短暂的异常或波动,接受数据就会出现短暂的中断,影响你的下载或者上传的效率,但是缓冲区解决了上传下载的传输效率的问题,带来了粘包问题

1.2收发的本质:不一定是一收一发

2.为什么产生黏包

  • 主要原因 : TCP称为流失协议, 数据流会杂糅在一起, 接收端不清楚每个消息的界限, 不知道每次应该去多少字节的数据

  • 次要原因 : TCP为了提高传输效率会有一个nagle优化算法, 当多次send的数据字节都非常少, nagle算法就会将这些数据合并成一个TCP段再发送, 这就无形中产生了黏包

3.什么是粘包?

  1. 接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)recv会产生黏包(如果recv接受的数据量(1024)小于发送的数据量,第一次只能接收规定的数据量1024,第二次接收剩余的数据量)
  2. 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)send 也可能发生粘包现象。(连续send少量的数据发到输出缓冲区,由于缓冲区的机制,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络)

4.产生黏包的两种情况 :

  • 发送端需要等待缓冲区满了才将数据发送出去, 如果发送数据的时间间隔很短, 数据很小, 就会合到一起, 产生黏包
  • 接收方没有及时接收缓冲区的包, 造成多个包一起接收, 如果服务端一次只接收了一小部分, 当服务端下次想接收新的数据的时候, 拿到的还是上次缓冲区里剩余的内容

注意:粘包不一定发生,如果发生了:

  1. 可能是在客户端已经粘了。
  2. 客户端没有粘,可能是在服务端粘了。

二、解决粘包问题的两种方式

1、通过send数据长度的方式来控制接收(low版)

粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

  • 服务端
from socket import *
import subprocessserver = socket(AF_INET,SOCK_STREAM)
server.bind(("127.0.0.1",8090))
server.listen(5)while True:print("等待连接...")conn,addr = server.accept()print(f"来自{addr}的连接")while True:try:cmd = conn.recv(1024)if not cmd:'''当你的程序运用在linux系统中,客户端强制断开链接,服务端会收到空的数据,在这种情况下就会退出与当前客户端的通讯循环,并关闭与该客户端的连接, 并回收当前客户端在服务端当前所占的系统资源,还有另一种情况就是客户端正常使用close()断开连接, 服务端也会收到空的数据,'''breakobj = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,)stdout = obj.stdout.read()stderr = obj.stderr.read()date = stdout + stderrdate_len = str(len(date)).encode("utf-8")  # 得到数据长度conn.send(date_len)  # 将数据的长度先发送conn.send(date)      # 再发送真实数据except Exception:'''这里解决的问题是:当你的程序运行在windows系统中,客户端强制断开链接,服务端就会引发异常. 因此,就可以使用异常处理机制来监测这种异常,此时一旦监测出这种异常,就代表客户端强制断开了链接.在这种情况下就会退出与当前客户端的通讯循环,并关闭与该客户端的连接, 并回收当前客户端在服务端当前所占的系统资源,'''breakconn.close()
  • 客户端
from socket import *client = socket(AF_INET,SOCK_STREAM)
client.connect(("127.0.0.1",8090))while True:cmd = input("请输入命令 >>").strip()if not cmd:# 这里解决的问题:"""当客户端输入为空的时候,在客户端的操作系统缓存中,并没有数据能发送给服务端. 硬件不能识别你这个空的数据,只有我们这种逻辑层面才有这种空的数据的存在.因此服务端的recv操作会在服务端的操作缓存中拿取数据,但是与此同时,服务端的操作系统缓存中并没有数据,于是服务端一直在recv堵塞状态.接着客户端因为send发送了数据,处于recv等待服务端发送回来的数据的状态,因而两者会处于一种recv堵塞的状态,"""continueclient.send(cmd.encode("utf-8"))"""粘包问题出现:问题1: 服务端发送回来的数据很多收不完,剩下的数据会保存在客户端的操作系统缓存中并不会丢失.问题2: 有可能服务端的数据过大,服务端并不是一次性的给你发送过来,可能分多次发送过来,所以这里接收的话可能接收的是部分.(这里是服务端的问题)"""date_len = int(client.recv(1024).decode("utf-8"))  # 先接收数据长度recv_len = 0'''粘包问题出现的原因:1、TCP是流式协议,数据像流水一样黏在一起,没有任何边界区分2、收数据没有收干净有残留, 就会与下次结果混淆在一起.解决核心: 保证每次都收干净, 不要任何残留- 服务端发送给客户端所需要发送数据的总大小,客户端拿到这个总大小,然后进行循环接收, 直至收取完毕, 结束本次收取数据的循环.'''while True:date = client.recv(1024)recv_len += len(date)print(date.decode("gbk"),end="")'''问题解决:尽最大量的收取客户端传输过来的数据量.- 缺点: 操作系统缓存有一定的容量,如果服务端发送过的数据量过大,本地操作系统缓存并不能一次性容纳,服务端的数据将会继续发送,因此,这种时候也不是一个好的解决方案,收取的数据也不一定收取全.'''if recv_len == date_len: break  # 当数据长度与接收到的数据长度相等则结束

为何low?

效率低 : 程序运行的速度远快于网络传输的速度, 如果在发送真实数据之前先send该数据的字节流长度, 那么就会放大网络延迟带来的性能损耗

2、使用 struct 模块实现精准数据字节接收(比较高效解决tcp协议的黏包方法)

解决粘包问题的核心就是:为字节流加上一个自定义固定长度的报头, 报头中就包含了该字节流长度, 然后只需要将整个数据加报头 send 一次到对端, 对端先取出固定长度的报头, 然后在取真实的数据

【温馨提示】:struct模块在前面已经介绍过了,👻struct模块传送门

  • 服务端
from socket import *
import subprocess
import struct
import jsonserver = socket(AF_INET, SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)
while True:print("Wait...")conn, client_addr = server.accept()print("已经连接:", client_addr)while True:try:cmd = conn.recv(1024)if not cmd: breakobj = subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE)stdout = obj.stdout.read()stderr = obj.stderr.read()'''1. 定制头部字典. 头部字典里面放,数据内容的大小,数据的描述信息,数据的md5值,等等数据,2. 接着将定制的头部json格式化成字符串,将字符串encode解码成bytes类型为了可以进行send操作3. 再使用struct将获取到的bytes数据的长度打包成固定长度, 这个第一个发送, 让客户端可以获取固定长度的bytes数据. 客户端获取到这个bytes类型的数据, 先decode解码, 再进行json反序列化拿到头部字典4. 客户端就可以通过获取到的头部字典拿到需要准备接收的数据的长度, 循环recv接收数据. 最终, 完美解决了黏包问题.'''# 制作一个报头字典(模板)header_dic = {"header_name": "jack","total_size": len(stderr) + len(stdout),"md5": "f3062575e23dbeb73c315ea525999295",}# 将报头(字典)序列化成字符串类型header_json = json.dumps(header_dic)# 将报头(字符串)转化成byte类型header_byte = header_json.encode("utf-8")# 将报头信息打包成4字节大小,里面包含的报头的长度header_size = struct.pack("i", len(header_byte))# 先发送打包的4bytes,4bytes包含了报头长度conn.send(header_size)# 在发送报头(客户端已经拿到了(解包出)报头的长度)conn.send(header_byte)# 发送真实数据data = stdout + stderrconn.send(data)# 可以合并到一起只send一次,但字符拼接又降低了效率(不推荐)# msg = header_size + header_byte + stdou t+ stderr# conn.send(msg)except Exception:breakconn.close()

问题: 前面说到多次send会扩大网络延迟带来的效率问题, 那为什么还要分四次 send ?

  • 其实在前面socket收发消息的原理图哪里就给出了答案,数据是先发送到字节操作系统的缓存内,时间间隔短,数据量小的会被和在一起发送,这就是TCP协议nagle优化算法做的事(有提升效率的功能,当然也带来了黏包问题)

  • 客户端

from socket import *
import json
import structclient = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:cmd = input("请输入你要执行的命令>>>:").strip()if not cmd: continue"""接收头部, 解析头部, 获取有用信息:1. 使用struct获取自定义固定长度, 2. 再拿到bytes格式json格式的dic内容, 3. 接着通过decode解码, 4. 然后再使用json反序列化获取dic, 5. 再由dic拿到数据量的大小,接着再通过循环收取数据,"""client.send(cmd.encode("utf-8"))# 接受byte_4byte_4 = client.recv(4)# 解包出报头长度header_len = struct.unpack("i", byte_4)[0]# 使用长度接受byteheader_byte = client.recv(header_len)# byte---->strheader_str = header_byte.decode("utf-8")# str---->dicheader_dic = json.loads(header_str)# 拿到真正数据的大小tatal_size = header_dic["total_size"]recv_size = 0while True:data = client.recv(1024)recv_size += len(data)print(data.decode("gbk"), end="")if recv_size == tatal_size: break

3、UDP没有粘包问题

udp 被称为数据报协议, 每次发送的数据都是一个数据报, 一次 sendto 对应一次 recvfrom, 不会产生黏包

udp 又被称为不可靠协议, 不可靠在哪里? 比如发送方发送了 10bytes 的数据, 而接收方只接收 8bytes 的数据, 那么剩下的两个 bytes 将会被丢弃, 并且在不同的平台有不同的表现, 下面我们来进行试验 :

  • windows平台下实验客户端
from socket import *client = socket(AF_INET,SOCK_DGRAM)msg = "1234567890".encode("utf-8")  # "utf-8"编码格式的数字和字母都是1bytes
msg2 = "12345".encode("utf-8")      # 发送 5bytes
msg3 = "123".encode("utf-8")        # 发送 3bytesclient.sendto(msg,("127.0.0.1",8989))
client.sendto(msg2,("127.0.0.1",8989))
client.sendto(msg3,("127.0.0.1",8989))
  • 服务端
from socket import *server = socket(AF_INET,SOCK_DGRAM)
server.bind(("127.0.0.1",8989))date,addr = server.recvfrom(8)    # 发送方发送 10bytes 只接收 8bytes
date2,addr2 = server.recvfrom(3)  # 发送方发送 5bytes 只接收 3bytes
date3,addr3 = server.recvfrom(1)  # 发送方发送 3bytes 只接收 1bytesprint(date.decode("utf-8"))
print(date2.decode("utf-8"))
print(date3.decode("utf-8"))
  • 运行结果如下:

image-20210121193118955

  • linux平台运行下实验客户端
from socket import *client = socket(AF_INET,SOCK_DGRAM)msg = "1234567891".encode("utf-8")
msg2 = "12345".encode("utf-8")
msg3 = "123".encode("utf-8")client.sendto(msg,("192.168.12.53",8848))
client.sendto(msg2,("192.168.12.53",8848))
client.sendto(msg3,("192.168.12.53",8848))
  • 服务端
from socket import *
server=socket(AF_INET,SOCK_DGRAM)
server.bind(("192.168.12.53",8848))
date,addr=server.recvfrom(8)
date2,addr2=server.recvfrom(3)
date3,addr3=server.recvfrom(1)
print(date.decode("utf-8"))  # 注意:如果发送方发送的是汉字, 一个汉字三个字节, >如果接受不完整解码出来会报错
print(date2.decode("utf-8"))
print(date3.decode("utf-8"))
  • 运行结果如下:

image-20210121202839709


http://chatgpt.dhexx.cn/article/OLBhAfqu.shtml

相关文章

粘包

粘包 一、什么是粘包二、为什么会粘包三、粘包解决思路 一、什么是粘包 粘包是指发送方发送的若干数据到接收方,而接收方在接收数据时这些数据粘在一起,后一包数据头紧接着前一包数据尾部。 二、为什么会粘包 首先了解一下socket收发消息原理&#xff1…

网络通讯中粘包的处理

参考:网络通讯中粘包的处理 - 走看看 在网络通讯中,不仅仅是TCP通讯,也包括串口通讯中,我们经常会遇到数据包粘连的问题,本文详细介绍粘包问题产生的原因和解决办法。 一、粘包定义 TCP 传输中,客户端发送…

什么是粘包?

TCP/IP 协议簇建立了互联网中通信协议的概念模型,该协议簇中的两个主要协议就是 TCP 和 IP 协议。TCP/ IP 协议簇中的 TCP 协议能够保证数据段(Segment)的可靠性和顺序,有了可靠的传输层协议之后,应用层协议就可以直接…

【HUST】信息系统安全:Ret2libc多函数调用,ASLR两种情况(2)

注:感谢这位大佬的帮忙,没有他我估计还在github里面或者其他博客里面瞎找小雨aaa Ret2libc:Return to libc,顾名思义,就是通过劫持控制流使控制流指向libc中的系统函数,从而实现打开shell等其他工作。 在本次作业中,…

Linux ALSA音频工具

参考: ALSA 音频工具 amixer、aplay、arecord Linux Alsa ALSA的配置文件 音频录制——arecord 音频播放——aplay 音频配置——amixer alsamixer与amixer的区别 alsamixer是Linux音频框架ALSA工具之一,用于配置音频各个参数; alsamixer是基于文本图形…

[pwn]ROP:绕过ASLRNX

[详细] ROP:绕过ASLR&NX 这次使用的程序是Defcon - 2015初赛题目,r0pbaby,也是一道经典的pwn题目了。 程序链接:https://pan.baidu.com/s/1kr6z_crZfW7qNjtASmRMGw 提取码:eajs NX策略是指在栈中的代码不会被执行…

ORA-445报错与ASLR

数据库多次出现ORA-00445: background process "J002" did not start after 30 seconds报错及ORA-3136错误 查看相关文档(文档 ID 1600807.1),两个报错都可能与内存压力过大有关 另外关于ORA-00445还有另一篇文档提到,在Oracle启用ASLR会无法…

[二进制学习笔记]Ubuntu20.04关闭开启ASLR

文章目录 Ubuntu20.04关闭开启ASLR Ubuntu20.04关闭开启ASLR ​ ASLR(Address space layout randomization)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者…

ASLR和PIE的区别

总结:ASLR 不负责代码段以及数据段的随机化工作,这项工作由 PIE 负责。但是只有在开启 ASLR 之后,PIE 才会生效。

【HUST】信息系统安全:Ret2libc多函数调用,ASLR两种情况(1)

Ret2libc:Return to libc,顾名思义,就是通过劫持控制流使控制流指向libc中的系统函数,从而实现打开shell等其他工作。 在本次作业中,我们的目标是通过运行stack.c程序来访问系统上的/tmp/flag程序的内容,其中,可以看到…

Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC)

1. ASoC的由来 ASoC--ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系。在ASoc出现之前,内核对于SoC中的音频已经有部分的支持,不过会有一些局限性&#xff1a…

astrill android,Astrill

你想知道你所用的网络速度是多少吗?你想在全球任何地方都可以使用到手机网络吗?你想在需要下载文件时定位到信号最强的地方进行传输吗?小编今天为企业家和用户安利一款手机测速游戏——Astrill APP!用户可以随时随地的查询到自己的…

Linux下 ASLR功能与 -no-pie 选项说明

一. Linux下ASLR功能 1. ASLR 技术介绍 ASLR 技术是一种针对缓冲区溢出的安全保护技术。 ASLR,全称为 Address Space Layout Randomzation,地址空间布局随机化。ASLR 技术在 2005 年的 kernel 2.6.12 中被引入到 Linux 系统,它将进程…

ASLR和PIE的区别和作用

ASLR和PIE的区别和作用 ASLR的作用 首先ASLR是归属于系统功能的, aslr是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置&…

Linux下关闭ASLR(地址空间随机化)的方法

##0x00 背景知识 ASLR(Address Space Layout Randomization)在2005年被引入到Linux的内核 kernel 2.6.12 中,当然早在2004年就以patch的形式被引入。随着内存地址的随机化,使得响应的应用变得随机。这意味着同一应用多次执行所使用内存空间完全不同&…

ASLR技术

简述 ASLR(Address Space Layout Randmoization,地址空间布局随机化)是一种针对于缓冲区溢出的安全保护技术。 windows 内核版本 OS内核版本windows 20005.0windows XP5.1windows Server 20035.2windows Vista6.0windows Server 20086.0wi…

ASLR

ASLR 一、ASLR是什么?二、测试ASLR技术1.一个简单的源文件2.生成ALSR.exe与ALSR_no.exe3.使用OllDbg调试器查看程序入口地址与栈地址3.1 ASLR.exe3.2 ASLR_no.exe 4.使用CFF Explorer查看PE文件信息4.1 重定位表的区别4.2 IMAGE_FILE_HEADER/Characteristics属性4.3…

[ASLR,地址空间,Linux,随机化,Windows]ASLR 是如何保护 Linux 系统免受缓冲区溢出攻击的

地址空间随机化(ASLR)是一种内存攻击缓解技术,可以用于 Linux 和 Windows 系统。了解一下如何运行它、启用/禁用它,以及它是如何工作的。 -- Sandra Henry-stocker 地址空间随机化(Address Space Layout Randomization)&#xff0…

ALSR

一 ALSR介绍: 1.1定义 aslr是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技…

如何提高强化学习算法模型的泛化能力?

深度强化学习实验室 官网:http://www.neurondance.com/ 来源:https://zhuanlan.zhihu.com/p/328287119 作者:网易伏羲实验室 编辑:DeepRL 在深度学习中,模型很容易过拟合到参与训练的数据集。因此,深度学习…