TCP知识补遗
在讲解TCP窗口的知识之前,关于TCP数据包还有几个知识点是需要补充讲解一下的。这里我们打开实验文件Lab9-1.pcap:
在之前的课程中我曾经讲过,TCP提供有序的数据传输,因此每个数据段都要标上一个序列号,也就是Seq的值。这样当接收方收到乱序的数据包时,就可以根据这个序列号进行重新排序了。这里我们并不需要知道Seq的起始值是怎么计算的,但是必须要理解这个值的增长方式。如上图所示,1号数据包Seq的值为4131969696,这个数据包的长度,也就是Len的值为1448,那么2号数据包的Seq的值就应当是4131969696+1448=4131971144,与2号数据包的Seq值是吻合的。可见,Seq的值是依据上一个数据包的Seq值加上长度得来的。这个Seq值是由这两个数据包的发送方,也就是10.32.106.159维护的。
由于TCP是双向的,一个连接中的双方都可以是数据的发送方,所以双方各自都会维护一个Seq值,上图中的1、2、4、5号数据包有自己的Seq值,而3号和6号数据包的Seq值则是由10.32.106.62维护的。3号数据包Seq的值为735349110,Len的值为0,所以6号数据包的Seq值依旧是735349110。
这里需要注意的是,比如1号数据包的Len的值为1448,但是这个长度是不包括TCP头的长度的。而3号和6号数据包的Len的值尽管为0,但其实这里面是包含有TCP头的。TCP头部包含有丰富的信息,因此大家千万不要误以为Len的值是0就没有意义,其实Len是0的情况还是很常见的。
然后我们回过头再看一下2号数据包。这里它发送了“Seq=4131971144,Len=1448”的数据包,那么当对方收到这个数据包之后,应当回复的确认号,也就是Ack的值就应该是4131971144+1448=4131972592,意味着已经收到了4131972592之前的所有字节数据。而我们看到3号数据包的Ack的值与此是吻合的,说明网络传输是正常的。事实上,接收方所回复的Ack的值其实就是发送方的下一个Seq的值,所以我们可以看到4号数据包的Seq的值也等于4131972592。
这里需要注意的是,3号数据包是对2号数据包成功接收的确认,但是为什么没有收到1号数据包的确认数据包呢?其实3号数据包在确认4131972592的时候,就说明了序列号小于4131972592的所有字节都收到了,也就相当于确认了1号数据包里面所包含的数据,可见TCP的确认是可以累加的。
在TCP连接中,因为双方都可以是接收方,所以他们各自除了会维护各自的Seq值以外,还会维护各自的Ack的值,由于在第3和第6号数据包中,10.32.106.62并未发送任何实质性的数据,也就是Len的值为0,于是接收方,也就是10.32.106.159的Ack的值就没有任何变化。
这些参数对于我们学习网络分析是很重要的。因为一旦将这些原理和概念了然于胸,那么接下来对于TCP的深入学习,理解TCP更加深层次的特性就会有事半功倍的效果。比如如果数据包出现了乱序的情况,那么接收方只要依据Seq的值从小到大进行排列就可以了,这样就保证了数据传输的有序性。或者当出现了丢包的情况,接收方只要通过前一个Seq+Len的值再减去下一个Seq的差,就可以判断丢了哪些包。可以举一个例子,假设10.32.106.62并未收到2号数据包,那么这里可以先计算一下1号数据包的Seq+Len的值,即4131971144,再减去下一个数据包的Seq,结果是1448,那么就可以知道有Len=1448的数据包丢失了。那么这就保证了TCP数据传输的可靠性。
TCP窗口的原理
在TCP协议中,实现了滑动窗口的机制,可以用来检测什么时候发生了数据包的丢失,并且调整数据的传输速率以避免丢失情况的加剧。滑动窗口机制利用数据接收方的接收窗口来对数据流进行控制。
接收窗口是数据接收方自己依照实际情况而定下来的值,保存在TCP的头部信息中。这个值告诉了发送方自己希望在TCP缓冲空间中保存多少数据。这个缓冲空间是数据在可以向上传递到等待处理数据的应用层协议之前的临时存储空间。所以,发送方一次只可以发送Window Size所指定的数据量。而为了传输更多的数据,接收方必须要发送确认数据包,表示之前的数据已经接收到了。同时也必须要处理占用着TCP缓冲区的数据,从而清空缓冲区,以接收新的数据。下图则对此进行了说明:
在上图中,客户端(发送方)正在向接收窗口大小为5000字节的服务器(接收方)发送数据。首先发送了2500字节,于是服务器可用的缓存空间就减少到了2500字节。接下来客户端又发送了2000字节,于是缓冲区就剩下了500字节。此时服务器发送数据确认的数据包,并处理缓存空间,于是得到了空的缓冲区。那么接下来的过程与上面的类似,客户端又发送了3000字节以及1000字节,从而将缓冲区减少到了1000字节。服务器再次对这些数据进行确认,并处理缓冲区的内容。
TCP窗口的调整
真实的网络环境中,数据的接收方往往需要对窗口的大小进行调整,毕竟接收方往往并不能够及时地处理缓冲区中的数据。 当一台繁忙的服务器需要处理来自多个客户端的数据包时,服务器清空缓冲区的速度就会减慢,从而无法腾出更多的缓冲区来接收数据。如果此时对网络流量不进行控制,就会导致数据包的丢失和损坏,因此,调整接收窗口的大小就很有必要了。也就是在回复ACK数据包时,同时告诉发送方,减小窗口的大小,如下图所示:
在上图中,服务器一开始声明的窗口大小是5000字节。客户端发送了2000字节,接下来又发送了2000字节,于是就剩下了1000字节的缓存空间。服务器发现如果客户端再这样发送数据,那么自己的缓冲空间就会被塞满,那么就会导致数据包的丢失。为了避免这个问题,服务器向客户端发送了一个确认数据包,里面也包含了新的窗口的大小为1000字节。于是,客户端就会减少发送的数据量,服务器可以按照能够接受的速率处理缓冲区的内容,保证网络的健康运行。那么当服务器处理完缓冲区中的数据,就可以考虑利用确认数据包,指定更大的窗口大小。
TCP的零窗口
在有些情况下,服务器可能无法处理客户端发送的数据,比如服务器出现故障等的情况。此时仅仅减小窗口的大小是不行,而需要将接收窗口的值直接设置为零值。也就是通过ACK数据包告诉客户端,窗口的大小是0,让客户端停止所有的数据传输,但是仍然会通过“保活数据包”来保持与服务器的连接。客户端会周期性地发送这样的数据包,以检测服务器的接收窗口的状态。一旦服务器排除了故障,能够再次处理数据,那么就会向客户端发送一个非零的窗口大小以恢复通信。下图展示了这个原理:
在上图中,服务器开始用5000字节的窗口接收数据。在从客户端接收到了两次2000字节的数据包之后,服务器出现了问题,无法再次处理来自客户端的数据。那么此时服务器就会发送一个ACK数据包,告诉客户端,窗口大小变成了0。于是客户端就暂停了数据的传输,接下来就给服务器发送保活数据包。在服务器解决了自身的问题之后,就会给客户端发送一个窗口大小非零的数据包,告诉客户端新的窗口大小为1000字节,于是客户端就可以继续发送数据了。
TCP滑动窗口数据包分析
下面我们通过分析几个实际的例子来看一下TCP的滑动窗口机制。打开实验文件Lab9-2.pcap:
这里我们主要关注的是Window Size的值,这个值可以通过查看数据包的Info列得出。可以发现,在前三个数据包中,这个值是在不断地缩小的。也就是从第一个数据包的8760减小到5840,再减小到2920,之后变成了0。窗口不断减小,是主机延迟增加的典型表现。此时注意一下Time列的值,可见窗口的缩小是在极短的时间内发生的。
第四个数据包是由数据的接收方发往数据的发送方的,目的是告诉对方自己不再接收任何数据。当自己的缓冲区数据处理完后,就告诉数据的发送方,自己的窗口大小变为了64240,可以继续接收数据了,于是接下来就恢复了数据的传输。 接下来我们再分析一下Lab9-3.pcap文件。可以看到,第一个数据包是一个正常的HTTP数据包,但是紧接着的2号数据包却是一个通知窗口为0的数据包:
与上一个实验文件所不同的是,接下来数据的接收方并没有发送窗口恢复的通知。所以数据的发送方只能不断地发送保活数据包来进行检查,可以看到捕获文件中的第3、5、7号数据包都是保活数据包,而数据接收方的回应全都是窗口值为0。 如果大家的Wireshark并没有标记出保活数据包,那么可以选择菜单栏的“Edit”->”Preferences”->”Protocols”->”TCP”,勾选“Analyze TCP sequence numbers”即可:
那么此时的零窗口数据包以及保活数据包就全都标记出来了。