tcp分片传输(TCP粘包与Nagle算法)(1)

tcp分片传输(TCP粘包与Nagle算法)(2)

Win32可以通过设置setsockopt为TCP_NODELAY的方式,来取消Nagle算法,这样就会出现只要有小数据段就会发送,接收方只要数据处理的过来,就可以避免TCP的拆包和粘包问题。但是这种方法会造成TCP传输效率降低,也不是一种理想的方法

也就是说协议栈为了避免一个字节就带上包头造成资源浪费,一般会启动tcp nagle算法,他会把这些1个字节的数据粘起来,然后一起发送。当然你可以做手动关闭,即禁用tcp Nagle算法,涉及TCP_NODELAY选项。下面来说下nagle算法规则:

为了尽可能的利用网络带宽,TCP总是希望尽可能多的发送足够大的数据。一个链接会设置MSS参数,因此TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据。即Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块,提高网络利用率。

Nagle算法的基本定义是:任意时刻,最多只能有一个未被确认的小段。所谓小段,指的是小于MSS尺寸的数据块,所谓未被确认,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已经收到。同时拥有以下几个规则:

1. 如果包长度达到MSS,则允许发送

2. 如果该包设置了FIN,则允许发送

3. 如果设置了TCP_NODELAY选项,则允许发送。

4. 未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送

5. 上述条件都未满足,但发生了超时(一般为200ms), 则立即发送

Nagle算法只允许一个未被ACK的包存在网络,它不管包的大小。因此它事实上就是一个扩展的停止协议,只不过他是基于“包停”等的,而不是基于“字节停”等的。即Nagle算法完全由TCP协议的ACK机制决定,这会带来一些问题。比如如果对端的ACK回复很快的话,Nagle事实上不会拼接太多的包,虽然避免了网络拥塞,但是网络总体的利用率很低。所以Nagle算法一般和延迟确认机制一起使用 TCP延迟确认机制介绍,但是也会存在一些情况导致网络超时。

同时会存在需要关闭Nagle的情况分别为:

1. 对端不向本端发送数据,并且对延时比较敏感的操作;这种操作没法捎带ack。

2. 写-写-读操作;对于此种情况,优先使用其他方式,而不是关闭Nagle算法:

1. 使用writev,而不是两次调用write,单个writev调用会使tcp输出一次而不是两次,只产生一个tcp分节,这是首选方法;

2. 把两次写操作的数据复制到单个缓冲区,然后对缓冲区调用一次write;

3. 关闭Nagle算法,调用write两次;有损于网络,通常不考虑;

粘包问题:

先来简单介绍下什么是粘包?

因为TCP是面向字节流的协议,没有消息保护边界,同时在TCP的首部没有表示数据长度的字段。一方发送的多个数据包,可能会被合并成一个大的数据包进行传输,这就是粘包。粘包情况有两种:

* 一种是粘在一起的包都是完整的数据包

* 另一种情况是粘在一起的包有不完整的包。

与粘包对应的还有一个概念:分包,他是指在出现粘包的时候我们的接收方要进行分包处理。(在长连接中都会出现) 数据包的边界发生错位,导致读出错误的数据分包,进而曲解原始数据含义。

tcp分片传输(TCP粘包与Nagle算法)(3)

原因:

发送方原因

TCP默认使用上面提到的Nagle协议,即会将小数据包合成大数据包一起发送出去。

接收方原因

接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据

tcp分片传输(TCP粘包与Nagle算法)(4)

从上面的可以看出,其实还是取决于因为接收方处理数据的速度太慢,发送方的数据发送的太快,导致缓冲区存在多个数据包,当你进行正常读取的时候,就会一次性取到几个包粘在一起

解决办法:

* 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。 分包解决策略 - socket接收指定长度的buf

* 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。

* 可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

* 通过设置TCP_NODELAY来关闭Nagle算法

* 通过设置PUSH标志到TCP的报头,发送方在发送数据的时候可以设置这个flag,这个标志会通知接收方将接收到的数据全部提交给接收进程,这里所说的数据包括与PUSH包一起传输的数据以及之前就为该进程传输过来的数据。Server收到之后就会立刻把这些数据提交给应用层进程,而不再等待额外是否有数据到达。设置PUSH标志也不能完全解决TCP粘包,只是降低了接收方粘包的可能性。实际上现在的TCP协议栈基本上都可以自行处理这个问题,而不是交给应用层处理,所以设置这个PUSH也不是一个理想方法

PS:

UDP因为不是面向“流”的,而且UDP是具有消息边界的,也就是说UDP的发送的每一个数据包都是独立的,因此UDP并不存在粘包的问题。

,