计算机网络协议第十章,TCP协议之滑动窗口

来源:互联网 发布:owncloud 源码 编辑:程序博客网 时间:2024/04/28 09:57

       本章围绕TCP协议的滑动窗口这一主题展开讨论,第一部分主要是对TCP滑动窗口的基础工作原理进行阐述,第二部分会深入的理解滑动窗口,第三部分会根据一个工作中实际遇到的例子来理解滑动窗口。


背景介绍

TCP协议提供可靠的数据传输,因此必须解决网络传输中带来的丢包和包乱序问题。可想而知,TCP协议栈需要一个接收缓存来处理接收到的数据报文,以保证其包顺序和完整性后提供给上层应用系统。TCP协议栈需要清楚网络实际的数据处理带宽和数据处理速度,以避免TCP接收缓存占用过多内存和匹配接收方实际处理能力,因此引入滑动窗口(Sliding Window)已通知发送方“接收方的最大接收能力”。

在阅读本文之前,希望你有一些TCP基础知识,了解TCP三步握手和TCP其他一些Flag标记的意义,这部分的工作可以阅读《TCP/IP卷一》。也可以阅读本博客的相关文章。


滑动窗口基础

这节主要讲述滑动窗口在TCP传输过程中如何体现的。

下图是TCP三次握手的SYN报文:


   可以看出滑动窗口的大小是65535字节,这是TCP三步握手中进行必要工作,经过TCP三步握手,双方能够感知对方的滑动窗口大小的初始值,用于控制向对方发送最大字节数,避免对方无法处理接收导致的丢包。

  上图还可以看出使用Syn包中使用Window scale选项。Window scale在TCP协议中定义是用于放大滑动窗口数值的途径。我们知道TCP中滑动窗口大小是有2个字节组成,也就是最大数65535,后来发现最大值不够用,因此需要Window scale来对其扩充,如果使用Window scale选项后,滑动窗口的实际大小为window size * 2^window scale。上图中windowscale为3,表示8倍。


   下图是TCP传输过程中的ACK报文:


前面我们了解到TCP三步握手后,传输双方能够获取对方的滑动窗口初始值,然而发送方发送一些数据给对方之后,如何再次或者对方的滑动窗口,上图已经说明是如何传递滑动窗口了,TCP通过Ack报文向对方反馈自己的滑动窗口,如果滑动窗口为0,发送方就应该暂停发送数据,等待对方滑动窗口恢复后在进行发送。可参见《TCP/IP卷一》图20-3。


滑动窗口深入

下图描述TCP协议栈有关滑动窗口几个重要变量。


*LastByteRead指向TCP接收缓存当前读到的位置。可以理解为上层应用系统最后一次read到的位置。

*NextByeExpected表示TCP接收缓存最后可读的位置。可以理解为最后一个连续、完整的数据报文。

*LastByteRcvd表示收到的最新报文段的位置。前面一段空白部分就是说明前面报文丢掉或者还未达到。

滑动窗口的大小为:最大接收缓存  -  (LastByteRcvd  -  LastByteRead)。可以简单这么理解。


下图描述发送方滑动窗口的示意图:



上图以颜色的方式分为4个部分,黑色框框就是滑动窗口,其大小由接收方控制。

*第一部分,表示已经发送并且接收方已经回复Ack的数据。

*第二部分,表示发送但是未收到Ack的数据。

*第三部分,表示还未发送出去的数据,但是接收方还有空间。

*第四部分,表示不能发送,接收方没有空间。



对比上图,红色部分表示新增加的部分表示接收方已经确认接收的数据。因此滑动窗口(黑色框框)的左边界向右运动,称为合拢运动。

第三部分用红色框框标记的为新的可用的数据,表示接收方已经释放相应的接收缓存(可以理解为上层应用程序read过该数据了),因此产生了新的可以空间。滑动窗口(黑色框框)的右边界相对上图也向右移动,称为张开运动。可参见《TCP/IP卷一》图20-5,图20-6

假设接收方回复【31-36Ack报文段中的滑动窗口值在变小(或者干脆为0),说明接收方TCP协议栈已经确认接收这部分数据,但是应用程序还未读取到该数据,这种情况下,滑动窗口的右边界就不会向右移动,也就是说不会做张开运动。此处假设是进一步说明滑动窗口的张开运动是依赖于接收方上报的滑动窗口值,而不是依赖Ack确认哪些数据报文已经被接收。

因此这种滑动窗口的合拢和张开运动是受接收方所控制,因为其依赖于接收方的Ack确认和接收方滑动窗口值的变化。


滑动窗口之案例

案例背景

有一个信令服务Server,有一个客户端Client。client分别运行在两种设备上,并且分别为window xp和window 2003的操作系统。
现象是其中xp运行client能够正常工作,2003运行的client出现一会连接一会断开的现象,而且反复如此无法正常工作。

问题分析

Server和window 2003的client 三步握手和注册信令都完成,但是后续的信令却迟迟无法得到回复,因此server端超时过后会主动断开连接,这就是解释了为什么反复断开连接的原因。
但是为什么相同的程序在window 2003上运行却数据迟迟无法回复呢?

下面抓取window 2003 client的三步握手报文的SYN,ACK分节,是server端回复的报文。

可以看出window scale=12, 滑动窗口的实际值应该是需要乘以2的12次方,就是乘以4096




上图是11号包的具体内容,我们看到windowsize对应的二进制数据是0x0010,然后乘以4096(2的12次方),也就是说服务端的滑动窗口大小是16*4096=65536计算得出的。

 但是我们看到13号报文中等待5秒钟才发送16个字节,而且这16个字节并不是完整业务的回应数据,只是数据开头的16字节,到此我们可以假设window2003的协议栈应该是没有正确的解析服务器[syn,ack]端中的windowscale,认为服务的滑动窗口大小是16字节,因此等待了5秒才发送16字节的报文。通过排查后续所有报文都有这个规律。



问题总结

该问题是由于window 2003的网络内核对TCP windowscale选项没有处理导致的,认为服务端滑动窗口的只有16个字节,而不是16*4096个字节,因此才等待了5秒之后才发送16个字节的报文,因此导致了服务端对数据报文迟迟没有响应。

linux中通过/proc/sys/net/ipv4/tcp_window_scaling 参数可以取消window scaling的机制,经过测试发送通信正常,也再次印证了上面的结论。

在socket编程中是无法设置window scaliing的,这个参数又是如何控制的呢?
引用TCP/IP卷一中的20.4小节原文:“插口API允许进程设置发送和接受缓存的大小,接受缓存的大小是该连接上所能够通告的最大窗口大小”。通过实验我们如果在connect调用之前设置RCV_BUFF选项,在调用connect会影响Syn段的滑动窗的大小,如果RCV_BUFF值大于65535肯定会使用windowscale选项。
其实也很好理解connect调用会触发SYN分节,因此之前设置才会影响滑动窗口的初始值,而滑动窗口只有16个bit,因此如果大于65535肯定需要使用window scale选项才能使滑动窗口大于65535,这也是windowscale选项被设计出来的目的。

小结

本章讨论了滑动窗口设计的目的,传递滑动窗口的方式,滑动窗口计算方式,发送方滑动窗口运动方式几个部分。通过以上几个部分的描述,希望能够让入门者有所收获。通过一个实际工作中遇到的问题,希望能够帮助对TCP滑动窗口的理解。


参考

《TCP/IP详解-协议》卷一     W.Richard Stevens

修订

初稿                                       2015-3-28               Simon



0 0
原创粉丝点击