TCP滑动窗口协议

TCP的首部中​有一个很重要的字段就是16位长的窗口大小,它出现在每一个TCP数据报中,配合32位的确认序号,用于向对端通告本地socket的接收窗口大小。也就是说,如果本地socket发送一个TCP数据,其32位确认序号是5,窗口大小是5840,则用于告诉对端,对端已经发出的4个字节的数据已经收到并确认,接下来,本地socket最多能够接收从第5个字节开始的5840个字节长度的数据。这是由接收方进行的一种流量控制,接收方通过告诉发送方自己所能够接收数据的大小,达到控制发送方发送速度的目的。
    结构体struct tcp_sock中有很多成员数据跟滑动窗口协议相关,需要注意的是这里讲的滑动窗口都是指本地socket的接收窗口。
    成员window_clamp表示滑动窗口的最大值,滑动窗口的大小在变化的过程中不能超出这个值。它在TCP连接建立的时候被初始化,被置为最大的16位整数左移窗口的扩大因子,因为滑动窗口在TCP首部中以16位表示,window_clamp太大会导致滑动窗口不能在TCP首部中表示。
    成员rx_opt是一个struct tcp_options_received结构体,它有两个成员snd_wscale和rcv_wscale,分别表示来自对端通告的滑动窗口扩大因子(本地发送数据报时需要遵守),和本地接收滑动窗口的扩大因子。snd_wscale从来自对端的第一个SYN中获取。rcv_wscale在本地socket建立连接时初始化,它赋值的原则是使16位整数的最大值左移rcv_wscale后,至少可以达到整个接收缓存的最大值。接收缓存最大值在协议栈中由全局变量mysysctl_rmem_max表示,它是256*(256+sizeof(struct sk_buff))后的值,为107520,但sysctl_tcp_rmem[3]所表示的接收缓存的上限更大,为174760,所以,取后者,这样的话,rcv_wscale的值几乎可以说是固定的,为2。所以window_clamp的值就是 65535 << 2 = 262140。可见,window_clamp的值超出了接收缓存的最大值,但这没有关系,因为在滑动窗口增长的时候,会考虑接收缓存的大小这个因素的。
    rcv_wnd表示当前的接收窗口的大小,这个值在接收到来自对端的数据后,会变动的。它的初始值取接收缓存大小的3/4跟MAX_TCP_WINDOW之间的最小值,MAX_TCP_WINDOW在系统中的定义为32767U。然后,还要根据mss的值作一个调整,调整逻辑是:如果mss大于3*1460,则如果当前的rcv_wnd大于两倍的mss,就取两倍的mss作为rcv_wnd的值;如果mss大于1460,则如果当前的rcv_wnd大于3倍的mss,就取3倍的mss作为rcv_wnd的新值;否则,如果rcv_wnd大于4倍的mss,就取4倍的mss作为rcv_wnd的新值,我们的实验环境的mss值为1448(因为tcp首部有12字节的时间戳选项),所以rcv_wnd最后被调整为1448*4=5792。
    rcv_ssthresh是当前的接收窗口大小的一个阀值,其初始值就置为rcv_wnd。它跟rcv_wnd配合工作,当本地socket收到数据报,并满足一定条件时,增长rcv_ssthresh的值,在下一次发送数据报组建TCP首部时,需要通告对端当前的接收窗口大小,这时需要更新rcv_wnd,此时rcv_wnd的取值不能超过rcv_ssthresh的值。两者配合,达到一个滑动窗口大小缓慢增长的效果。
    rcv_wup记录滑动窗口的左边沿,即落在滑动窗口中的最小的一个序号。这样的话,rcv_wup+rcv_wnd即为滑动窗口的右边沿,rcv_wup+rcv_wnd-rcv_nxt即为滑动窗口的空白部分。它的初始值为0,在移动滑动窗口时被更新。    以上是关于接收滑动窗口的几个相关数据,下面我们看看它们是如何运用在TCP协议的通讯中的。
    每次发送一个TCP数据报,都要构建TCP首部,这时,会调用my tcp_select_window选择窗口大小,窗口大小选择的基本思想是接收缓存剩余空间大小的3/4,但是不能超过rcv_ssthresh的大小。但是,如果这个新选择的窗口大小比当前窗口的剩余大小还小,则以当前窗口的剩余大小作为新窗口的大小。同时右移左边沿,令rcv_wup=rcv_nxt。这个新选择的窗口是受rcv_ssthresh限制的,一般不会有什么问题,但我们可以看到代码中还是作了一些上限判断,如果扩大因子为0,则窗口大小不能超过32767U,否则不能超过65535左移扩大因子后的值。
    每次接收到来自对端的一个TCP数据报,且数据报长度大于128字节时,我们需要调用mytcp_grow_window,增加rcv_ssthresh的值,一般每次为rcv_ssthresh增长两倍的mss,增加的条件是rcv_ssthresh小于window_clamp,并且rcv_ssthresh小于接收缓存剩余空间的3/4,同时tcp_memory_pressure没有被置位(即接收缓存中的数据量没有太大)。tcp_grow_window中对新收到的skb的长度还有一些限制,并不总是增长rcv_ssthresh的值。具体见函数代码。
    以上是关于接收窗口,下面简单看一下发送窗口。关于发送窗口,在struct tcp_sock中也有一些成员数据相关。
    snd_wl1记录发送窗口更新时,造成窗口更新的那个ACK数据报的第一个序号。它主要用于在下一次判断是否需要更新发送窗口。
    snd_wnd是发送窗口的大小,直接取值于来自对端的数据报的TCP首部。
    max_window记录来自对端通告的窗口的最大值。
    snd_una表示当前正等待ACK的第一个序号,而发送窗口实际上是在每次收到来自对端的ACK后,都会更新,所以,实际上snd_una成了发送窗口的左边沿。 

原文地址:https://www.cnblogs.com/qiaoyanlin/p/7680672.html