TCP粘包与拆包

2 篇文章 0 订阅

什么是粘包与拆包?

[网络来源]
tcp粘包和拆包这种说法是不准确的。在tcp的世界里本来就没有包何来的拆包粘包? 这种说法真的是误导人。TCP本来就是流式的处理方式,并没有package或者messsage的概念在里面,tcp非常类似一个文本文件的处理方式,tcp只是给你提供了一个写文件和读文件的接口,至于写入文本文件的是纯文本还是csv或者是xml,怎么写怎么读之后解析都是你自己的事情。

TCP协议是个“流”性质协议,它的底层根据二进制缓冲区的实际情况进行包的划分,会把上层(Netty层)的ByteBuf包,进行重新的划分和重组,组成一帧一帧的二进制数据。换句话说,一个上层Netty中的 ByteBuf包,可能会被TCP底层拆分成多个二进制数据帧进行发送;也有可能,底层将多个小的ByteBuf包,封装成一个大的底层数据帧发送出去。

当client发送消息到服务端,服务端只收到消息的一半,或者当连续发送两个消息到服务端,服务端同时收到这两个消息但无法解析。这就是TCP“粘包拆包”现象。

由于TCP"流"的特性以及网络状况,在进行数据传输时会出现以下几种情况.
在这里插入图片描述

假设我们连续两次分别发送两段数据data1和data2,在服务端有以下几种接收情况:
a.先接收到data1,然后接收到data2.
b.先接收data1的部分数据,然后接收data1余下的部分以及data2的全部.
c.先接收data1的全部数据和data2的部分数据,然后接收了data2的余下的数据.
d.一次性接收到了data1和data2的全部数据.

对于A这种情况正是我们需要的.而对于B,C,D的情况就是大家经常说的"粘包",就需要我们把接收到的数据进行拆包,拆成一个个独立的数据包.为了拆包就必须在发送端进行封包.

粘包

"粘包"可发生在发送端也可发生在接收端.

1.由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法.
简单的说,当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间,看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去.这是对Nagle算法一个简单的解释,C和D的情况就有可能是Nagle算法造成的.

2.接收端接收不及时造成的接收端粘包:
TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据.当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据.

产生原因(3种)

1.应用程序write写入的数据大于套接字缓冲区大小,这将会发生拆包。
2.发送数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。
3.进行MSS大小的TCP分段。
发送的数据大于MTU(最大传输单元)大小。 进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包。
3.以太网帧的payload大于MTU进行IP分片
4.接收方不及时读取套接字缓冲区数据,这将发生粘包。
在这里插入图片描述

MTU、MSS

mtu是网络传输最大报文包。mss是网络传输数据最大值。

在TCP通讯协议中TCP的每个包头的长度都是固定的,总长度不能超过MTU(最大传输单元),且数据长度不能超过MSS(MSS=MTU-20bytes(IP包头)-20bytes(TCP包头))。如果超过了MTU系统会进行拆包处理。

举个例子:
(1)假设MTU设置的长度为1500bytes则MSS为1460bytes。
(2)客户端发送了p1包数据大小2000bytes。
(3)系统判断总长度超过了MTU大小,需要拆包处理。
(4)拆成2个包p1_1和p1_2,p1_1的总长度=1460+20+20=1500,p1_2的总长度=2000-1460+20+20=580(5)发送包p1_1和包p1_2。

怎样封包和拆包

知道了粘包、拆包问题及根源,那么如何处理粘包、拆包问题呢?
最初遇到"粘包"的问题时,我是通过在两次send之间调用sleep来休眠一小段时间来解决.这个解决方法的缺点是显而易见的,使传输效率大大降低,而且也并不可靠.后来就是通过应答的方式来解决,尽管在大多数时候是可行的,但是不能解决象B的那种情况,而且采用应答方式增加了通讯量,加重了网络负荷(但是象FTP等协议采用的就是应答方式).再后来就是对数据包进行封包和拆包的操作.

封包

封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(以后讲过滤非法包时封包会加入"包尾"内容).包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义.根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包.、

拆包

对于拆包目前我最常用的是以下两种方式.
1.动态缓冲区暂存方式.之所以说缓冲区是动态的是因为当需要缓冲的数据长度超出缓冲区的长度时会增大缓冲区长度.

大概过程描述如下:
A,为每一个连接动态分配一个缓冲区,同时把此缓冲区和socket关联,常用的是通过结构体关联.
B,当接收到数据时首先把此段数据存放在缓冲区中.
C,判断缓存区中的数据长度是否够一个包头的长度,如不够,则不进行拆包操作.
D,根据包头数据解析出里面代表包体长度的变量.
E,判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,则不进行拆包操作.
F,取出整个数据包.这里的"取"的意思是不光从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉.删除的办法就是把此包后面的数据移动到缓冲区的起始地址.

这种方法有两个缺点.
1.为每个连接动态分配一个缓冲区增大了内存的使用.
2.有三个地方需要拷贝数据,一个地方是把数据存放在缓冲区,一个地方是把完整的数据包从缓冲区取出来,一个地方是把数据包从缓冲区中删除.这种拆包的改进方法会解决和完善部分缺点.

解决方案

通常的解决方案 TCP是不知道上层的业务数据的。所以在底层无法保证不发生粘包拆包,这个问题只能通过上层应用协议栈的设计来解决。
无论拆包还是粘包本质问题都是无法区分包界限,解决包界限的问题主要有以下几种方式:
(1)设置定长消息,比如定长100字节,不足补空格,接收方收到后解析100字节数据即为完整数据。但这样的做的缺点是浪费了部分存储空间和带宽。
(2)设置消息边界,消息数据使用特定分割符区分界限,比如使用换号符号做分割。
(3)设置的协议加消息头。把消息数据分成消息头和消息体,消息头存储消息开始标识及消息长度信息,服务端根据消息头中的长度向后读取解析数据。

UDP

对于UDP来说就不存在拆包的问题,因为UDP是个"数据包"协议,也就是两段数据间是有界限的,在接收端要么接收不到数据要么就是接收一个完整的一段数据,不会少接收也不会多接收.

其他

MTU、MSS、TCP、UDP 名词解释

MTU:最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小。
是包或帧的最大长度,一般以字节记。如果MTU过大,在碰到路由器时会被拒绝转发,因为它不能处理过大的包。如果太小,因为协议一定要在包(或帧)上加上包头,那实际传送的数据量就会过小,这样也划不来。大部分操作系统会提供给用户一个默认值,该值一般对用户是比较合适的。

MSS(Maximum Segment Size,最大报文段大小)
MSS=MTU-20bytes(IP包头)-20bytes(TCP包头)

TCP:传输控制协议,向上与用户应用程序进程接口,向下与网络层协议IP接口。
TCP提供一种面向连接的、可靠的字节流服务。

UDP是User Datagram Protocol的简称,中文名是用户数据报协议,是OSI参考模型中的传输层协议,它是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
UDP的正式规范是IETF RFC768。UDP在IP报文的协议号是17。

Socket:一个IP地址和一个端口号有时也称为一个套接字socket,socket pair(包含客户IP地址、客户端口号、服务器 IP地址和服务器端口号的四元组)可唯一确定互联网络中每个TCP连接的双方。IP+TCP端口唯一确定一个TCP连接。

参考资料

TCP拆包粘包
https://my.oschina.net/u/945573/blog/2982666

TCP选项之MSS
https://blog.csdn.net/xiaoyu_750516366/article/details/85316123

TCP包头结构
https://blog.csdn.net/xpmwgcwm/article/details/84398946

TCP,UDP,IP包头格式及说明
https://blog.csdn.net/qq_30549833/article/details/60139328

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty中的TCP问题是由于底层的TCP协议无法理解上层的业务数据而导致的。为了解决这个问题,Netty提供了几种解决方案。其中,常用的解决方案有四种[1]: 1. 固定长度的器(FixedLengthFrameDecoder):将每个应用层数据分成固定长度的大小。这种器适用于应用层数据长度固定的情况。 2. 行器(LineBasedFrameDecoder):将每个应用层数据以换行符作为分隔符进行分割分。这种器适用于应用层数据以换行符作为结束符的情况。 3. 分隔符器(DelimiterBasedFrameDecoder):将每个应用层数据通过自定义的分隔符进行分割分。这种器适用于应用层数据以特定分隔符作为结束标志的情况。 4. 基于数据长度的器(LengthFieldBasedFrameDecoder):将应用层数据的长度作为接收端应用层数据分依据。根据应用层协议中含的数据长度进行。这种器适用于应用层协议中含数据长度的情况。 除了使用这些器,还可以根据业界主流协议的解决方案来解决问题[3]: 1. 消息长度固定:累计读取到长度和为定长LEN的报文后,就认为读取到了一个完整的信息。 2. 使用特殊的分隔符:将换行符或其他特殊的分隔符作为消息的结束标志。 3. 在消息头中定义长度字段:通过在消息头中定义长度字段来标识消息的总长度。 综上所述,Netty提供了多种解决方案来解决TCP问题,可以根据具体的业务需求选择合适的解决方案[1][3]。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值