+ -
当前位置:首页 → 问答吧 → TCP协议中Window Scale Option问题【完全解决】

TCP协议中Window Scale Option问题【完全解决】

时间:2010-01-13

来源:互联网

大家好,我在继续研读Stevens的“TCPIP协议详解卷1”中,又遇到了关于window scale option的一个问题,望大家不吝赐教,下面是问题描述。


在24.4 Window Scale Option中有如下文字:
We saw an example of this option in Figure 18.20. The 1-byte shift count is between 0 (no scaling performed) and 14. This maximum value of 14 is a window of 1,073,725,440 bytes (65535 × 2^14).

这里我没有懂的是为啥scale的最大范围是左移14位,即2^14次幂,这个14是如何得到的?

我查阅了下rfc1323,其中有针对这个14的计算来由,但是不幸的是,该rfc中和14有关的地方我仍然没有明白,所以非常郁闷,请大家帮帮我,下面是rfc1323中有关的内容:

2.3  Using the Window Scale Option
        ...
      TCP determines if a data segment is "old" or "new" by testing
      whether its sequence number is within 2**31 bytes of the left edge
      of the window, and if it is not, discarding the data as "old".  To
      insure that new data is never mistakenly considered old and vice-
      versa, the left edge of the sender's window has to be at most
      2**31 away from the right edge of the receiver's window.

这里rfc1323在推导14这个数时,有上面一段话,其中说明了一个数据分段的序号如果距滑动窗口左边沿2**31次幂之内,则都不是old数据,这里我不明白这个31是如何得到的

[ 本帖最后由 jiufei19 于 2010-1-20 11:56 编辑 ]

作者: jiufei19   发布时间: 2010-01-13

我把rfc1323中如何计算出14为最大伸缩因子的文字列出,请大家帮忙看看
--------------------------------------------------------------------------------------

      TCP determines if a data segment is "old" or "new" by testing
      whether its sequence number is within 2**31 bytes of the left edge
      of the window, and if it is not, discarding the data as "old".  To
      insure that new data is never mistakenly considered old and vice-
      versa, the left edge of the sender's window has to be at most
      2**31 away from the right edge of the receiver's window.
      Similarly with the sender's right edge and receiver's left edge.
      Since the right and left edges of either the sender's or
      receiver's window differ by the window size, and since the sender
      and receiver windows can be out of phase by at most the window
      size, the above constraints imply that 2 * the max window size
      must be less than 2**31, or

           max window < 2**30

      Since the max window is 2**S (where S is the scaling shift count)
      times at most 2**16 - 1 (the maximum unscaled window), the maximum
      window is guaranteed to be < 2**30 if S <= 14.  Thus, the shift
      count must be limited to 14 (which allows windows of 2**30 = 1
      Gbyte).  If a Window Scale option is received with a shift.cnt
      value exceeding 14, the TCP should log the error but use 14
      instead of the specified value.

可以看出2**31次幂的结果是最关键的地方,这个明白了,后面的就说得通了,我就是看不懂这个地方,我的疑问是既然tcp的序号空间范围是32bit,超过后会回绕序号,那么2**31次幂的意思就是将该范围划分为相等的2部分,即2**32/2=2**31,为啥是这样呢?

另外,我对原文中所提到的“old”和“new”的含义不是很明白其准确含义?

[ 本帖最后由 jiufei19 于 2010-1-16 11:01 编辑 ]

作者: jiufei19   发布时间: 2010-01-14

另外一篇和此问题相关的文字也列出,请大家帮忙看看
--------------------------------------------------------------------------------------

      Even though a 32-bit space is now allowed for the actual window size, the maximum data size is not 4 GB (2**32 ), but rather 1 GB (2**30 ). This is done so that a TCP receiver can uniquely identify an incoming data segment as a new segment. There is no possibility of old and new segments with the same sequence number arriving out of turn and confusing the receiver, as sequence numbers themselves are in the 32-bit space.

为啥这样只利用原来的4G范围的四分之一,即1G,就能保证接收方不会混淆老数据和新数据?

(1) 什么是所谓的新数据,什么是所谓的老数据?
(2) 难道二分之一不行?

[ 本帖最后由 jiufei19 于 2010-1-16 10:59 编辑 ]

作者: jiufei19   发布时间: 2010-01-14

tcp的最大序号是多少?

作者: hritian   发布时间: 2010-01-16

最大序号是 2**32-1 呀

作者: jiufei19   发布时间: 2010-01-16

经过我反复理解,认为这里的old和new是指32bit序号发生回绕后新分段的序号和旧分段的序号相同,于是导致接收方混淆,不过仍然不能理解为啥在2**31次以内就不会造成混淆的原因

作者: jiufei19   发布时间: 2010-01-16

自己顶下,这2天我又反复阅读了rfc1072等文档,那几个rfc似乎给出了如下示意图,我对此只是模模糊糊理解,但是详细分析,又觉得不甚了了

snd.una     
snd.nxt     snd.una + snd.wnd
  +----------+
  |  Sender    |
  +----------+          2**31                          2**32
  +---------------------+-----------------------+
                +-----------+
                | Receiver    |
                +-----------+
           rcv.wup      rcv.wup+rcv.wnd
           rcv.nxt

[ 本帖最后由 jiufei19 于 2010-1-18 10:40 编辑 ]

作者: jiufei19   发布时间: 2010-01-18

我自己尝试下先解决2**31次幂的那个问题,如有错误,请大家拍砖

1、序号空间大小是2**32,而所有和窗口大小有关的值都应满足2的整数次幂,所以窗口大小只能是2**32,2**31,2**30,...,所以现在我们要分析的就是2**32不能被用来表示最大可以使用的窗口值

2、在建立连接的TCP收发双方初始时刻,这个时刻是snd.una和rcv.wup + rcv.wnd之间相差最大的时候,于是我们有如下关系:
snd.una           
snd.nxt               snd.una + snd.wnd
  +---------------------+
  |     Sender                |
  +---------------------+
     
  +---------------------+----------------------+
  0                            2**31-1                  2**32-1
   
  +---------------------+
  |    Receiver               |
  +---------------------+
rcv.wup      rcv.wup+rcv.wnd
rcv.nxt

根据TCP滑动窗口的概念,其表示发送方在未收到接收方的确认前,所能发送的最大数据,那么根据上图我们可以看出如果这个最大数据为2**32的话,即表示一个窗口就可以填满整个序号空间的值,那么此时当发送方收到确认后,序号回绕就立刻发生了,因此就会造成之前窗口中部分被延迟分段的序号和新窗口中的序号发生重叠,显然,由于此时TCP还没有引入timestamps的选项,所以这种情况导致接收方产生混乱。

因此,2**32不能作为最大的窗口值,并且由于窗口大小必须是2的整数次幂,于是我们就知道了上图中|snd.una - (rcv.wup + rcv.wnd)| <= 2**31,即这个差的绝对值最大只能是32bit序号空间的一半,此时,发方最多发送2**31字节数据,若还没有收到收发的ACK,则发方必须等待,显然这个初始状态的差值是最大的,并且只有满足在此2**31范围内,则老数据和新数据(序号回绕后)绝对不会重叠。以后发送窗口和接收窗口都会随着时间平移,但保持这个差值不大于2**31是恒定的

[ 本帖最后由 jiufei19 于 2010-1-18 14:12 编辑 ]

作者: jiufei19   发布时间: 2010-01-18



QUOTE:
原帖由 jiufei19 于 2010-1-18 10:38 发表
自己顶下,这2天我又反复阅读了rfc1072等文档,那几个rfc似乎给出了如下示意图,我对此只是模模糊糊理解,但是详细分析,又觉得不甚了了

snd.una     
snd.nxt     snd.una + snd.wnd
  +----------+
  | ...




查了一下,没有找到2**31的出处。可能没有我们想像的那么复杂,可能就是一个判断sequence wrap问题的一个最loose的规定(在PAWS出现之前)。在实际的tcp/ip stack的实现中,窗口不可能有那么大,所以一般就是判断收到的segment的sequence有没有在窗口内就可以了。

"the sender and receiver windows can be out of phase by at most the window size"
sender和receiver的窗口有可能out-of-phase,最坏的情况有可能就是sender发了一窗口的数据,receiver都收到了,但是reciever的ack都没有到达sender,这样就出现了rfc1323所说的out-of-phase一个窗口的情况。receiver想收rcv.wup+rcv.wnd开始的数据,而sender可能重发它窗口的left edge的数据。

sender窗口的left edge和receiver窗口的right edge不能超过2**31。否则reciever就会直接把sender可能重发的数据直接丢掉了。在这种情况下得到了max window是2**30。

snd.una     
snd.nxt     snd.una + snd.wnd
  +----------+
  |  Sender  |
  +----------+          2**31                          2**32
  +-----------------------+-----------------------+
              +-----------+
              | Receiver  |
              +-----------+
           rcv.wup      rcv.wup+rcv.wnd
           rcv.nxt

作者: eexplorer   发布时间: 2010-01-19

感谢eexplorer的答复,不过我对你的如下说明的意思还没有太明白,请明示:

sender窗口的left edge和receiver窗口的right edge不能超过2**31。否则reciever就会直接把sender可能重发的数据直接丢掉了。在这种情况下得到了max window是2**30。

>> 我们先假定那个2**31的概念是先验的,不需要推导,根据本帖子的如下图示,的确表示收发双方out-of-phase的情况
snd.una     
snd.nxt     snd.una + snd.wnd
  +----------+
  |  Sender    |
  +----------+          2**31                          2**32
  +---------------------+-----------------------+
                +-----------+
                | Receiver    |
                +-----------+
           rcv.wup      rcv.wup+rcv.wnd
           rcv.nxt

那么当接收方的右边界rcv.wup+rcv.wnd - snd.una > 2**31后,为啥接收方会丢掉重发的数据?或者换句话讲,即便不大于2**31,重发的数据因为之前已经被收到了,只是ACK尚未到达发送方,因此发送方若重发的话,这样就不丢掉了吗?另外接收窗口右边界的位置是受接收方应用进程的控制,所以,假设在发生out-of-phase后,接收方应用若读取了部分数据,那么其右边界可能超过了2**31的位置,难道这个情况不可能吗?我感觉我某个地方对滑动窗口理解有错误了。这里我感觉正是因为2**31预先认为是正确的,所以才有上图中发送和接收窗口错开的情况,并且因为初始时刻接收方缓存大小是固定的,而且假定一直不发生改变,于是我们有:

snd.una     
snd.nxt     snd.una + snd.wnd
  +----------+
  |  Sender |
  +----------+          2**31                          2**32
  +---------------------+-----------------------+
  +-----------+
  | Receiver |
  +-----------+
  rcv.wup      rcv.wup+rcv.wnd
  rcv.nxt

这样,当发送方发送了这里的2**30字节,即整个窗口后,接收方都收到后,并且返回的ACK假定都未到达发方,此时才发生了上述的out-of-phase情况

PS:
之所以我对这里2**31,窗口扩大因子14等等有疑问是我感觉我对滑动窗口的概念存在不清晰的地方,望不吝赐教哈!

[ 本帖最后由 jiufei19 于 2010-1-19 10:46 编辑 ]

作者: jiufei19   发布时间: 2010-01-19

> 那么当接收方的右边界rcv.wup+rcv.wnd - snd.una > 2**31后,为啥接收方会丢掉重发的
> 数据?或者换句话讲,即便不大于2**31,重发的数据因为之前已经被收到了,
> 只是ACK尚未到达发送方,因此发送方若重发的话,这样就不丢掉了吗?

是同样都丢掉了,但是情况稍微有些不同。按照2**31的规定,如果receiver收到超过2**31的包的话,就因为too old而直接丢掉了。而如果在2**31的范围内的话,就需要处理duplicate包的情况,即需要告诉sender它收到了duplicate的数据包。


> 另外接收窗口右边界的位置是受接收方应用进程的控制,所以,假设在发生out-of-phase后,
> 接收方应用若读取了部分数据,那么其右边界可能超过了2**31的位置,难道这个情况不可能
> 吗?

这当然是有可能的,但是我们只是在讨论一种极端的情况,以推出max window的大小,没必要深究更detail的情况。

作者: eexplorer   发布时间: 2010-01-19

另外,我还有一个不解就是,为啥不能出现下面的这个方式呢,即窗口大小为2G,这样就恰好应证了我之前的那个2**31的分析了:

snd.una     
snd.nxt     snd.una + snd.wnd
  +---------------------+
  |  Sender                  |
  +---------------------+
                            2**31                          2**32
  +---------------------+-----------------------+
                               +-----------------------+
                                |          Receiver          |
                               +-----------------------+
                           rcv.wup      rcv.wup+rcv.wnd
                           rcv.nxt

[ 本帖最后由 jiufei19 于 2010-1-19 13:42 编辑 ]

作者: jiufei19   发布时间: 2010-01-19

仔细阅读了eexplorer的解释和说明,以及我再次阅读那几个rfc文档,我忽然想到了这样一个似乎“完善”的说明:

1、根据我最前面的分析,已经知道了|snd.una - rcv.wup+rcv.wnd|<=2**31是合理的,即发送窗口最左边的数据序号和接收窗口的最大可接收数据序号只要相差在2**31,即半个32bit序号空间大小,那么old和new数据就肯定不会混淆(这是肯定的,因为一半的使用率完全限制了回绕造成的冲突)。不过要注意这里的不会混淆的代价是网络速率虽然可能较高了,但不是很高,也即在不采用PAWS的算法下,该tcp的吞吐量不是很受影响,换句话讲,如果网络速度非常高,则限制为32bit一半的序号空间的做法,虽然可以避免回绕带来的混淆问题,但是却会造成严重的吞吐量下降,此时必须引入PAWS算法来避免序号的快速回绕



2、在满足1的序号条件下,则发送和接收的最大out-of-phase情况只能是如下情况,此时实际发送和接收窗口只能是2**31的一半,即2**30

snd.una     
snd.nxt     snd.una + snd.wnd
  +----------+
  |  Sender   |
  +----------+
                               2**31                          2**32
  +----------------------+-----------------------+
                 + ----------+
                 |  Receiver   |
                 +- ---------+
        rcv.wup      rcv.wup+rcv.wnd
        rcv.nxt

[ 本帖最后由 jiufei19 于 2010-1-19 13:44 编辑 ]

作者: jiufei19   发布时间: 2010-01-19



QUOTE:
原帖由 jiufei19 于 2010-1-19 13:14 发表
另外,我还有一个不解就是,为啥不能出现下面的这个方式呢,即窗口大小为2G,这样就恰好应证了我之前的那个2**31的分析了:

snd.una     
snd.nxt     snd.una + snd.wnd
  +---------------------+
  |  ...




在这种情况下,如果receiver收到一个seq = 0的数据包,那它就不能区分是新的还是旧的了。可能这就是tcp规定2**31的原因了。。。

作者: eexplorer   发布时间: 2010-01-19

eexplorer是否是这个意思:

因为之前一个窗口的seq=0的序号分段虽然已经收到了,导致接收窗口的右边沿到了2**32-1,但是由于此时发送方没有收到应答,于是可能重发了一个seq=0的分段,但是此时即将发送的一个新分段的seq也为0,于是接收方可能将这个新0分段当成了老的

作者: jiufei19   发布时间: 2010-01-19



QUOTE:
原帖由 jiufei19 于 2010-1-19 13:50 发表
eexplorer是否是这个意思:

因为之前一个窗口的seq=0的序号分段虽然已经收到了,导致接收窗口的右边沿到了2**32-1,但是由于此时发送方没有收到应答,于是可能重发了一个seq=0的分段,但是此时即将发送的一个 ...



sender重发一个seq = 0的segment,但是receiver不知道这个segment是新的还是旧的,即应该丢掉还是放到自己的receive queue里。

作者: eexplorer   发布时间: 2010-01-19

恩,就是这个意思,那么再请eexplorer看看我在13楼做的一个该问题的小结是否合理,谢谢!

作者: jiufei19   发布时间: 2010-01-19

恩,就是这个意思,那么再请eexplorer看看我在13楼做的一个该问题的小结是否合理,谢谢!

作者: jiufei19   发布时间: 2010-01-19



QUOTE:
原帖由 jiufei19 于 2010-1-19 14:07 发表
恩,就是这个意思,那么再请eexplorer看看我在13楼做的一个该问题的小结是否合理,谢谢!



> 如果网络速度非常高,则限制为32bit一半的序号空间的做法,虽然可以避免回绕带来的混淆问题,但是却会造成严重的吞吐量下降,
> 此时必须引入PAWS算法来避免序号的快速回绕


我始终觉得,引入2**31的限制只是在理论上做了一些限制,用来推导一些别的东西,如max window size的问题。
实际情况下:
1. 不可能有那么大的窗口
2. PAWS应该更常用
所以问题没这么严重。

不过佩服你的学习钻研精神,我也学到了不少东西。

作者: eexplorer   发布时间: 2010-01-19

谢谢eexplorer的夸奖哈,我一直比较注意这些细节,也许有点过于?不过这个概念和精确理解滑动窗口的原理是有密切联系的,我一直感到自己对滑动窗口的概念没有精确理解,泛泛讲还觉得自己掌握了,但是一到代码有时就有点糊涂了:(

另外,2**31一定不是随意想出来的数,肯定是在一个条件下得到的,我认为就是按照如下2点:
1、窗口大小是2的整数次幂
2、限制为2**32的一半,理论上肯定序号回绕肯定不会导致混淆问题出现,因为前后序号相差只能最大达到2**31,因此当回绕后,最早前一个窗口的序号已经越过了2**31,所以如果用刚才的例子seq=0,则此时该序号一定是新的数据了。不过这里一定要注意这样来保证序号回绕问题不会产生麻烦的代价就是吞吐量下降了,否则PAWS也没有必要引入

再次感谢eexplorer的热心哈,总是在我需要的时候你给出的解答能让我进一步理解,虽然有时不能完全彻底

作者: jiufei19   发布时间: 2010-01-19

经过和eexplorer的探讨,我昨天仔细将他的说明和自己的考虑重新认真思索了一遍,终于彻底解决了2**31的问题,下面我将分析详细说明如下,希望能给大家帮助(假如没有错误的话:" />:" /> )

1、参考图1
下载 (22.59 KB)
2010-01-20 12:02


1、下面我们分析下对应2**32的序号空间,究竟发送和接收窗口需要多大?
a)我们首先假定滑动窗口大小刚好容纳整个32bits序号空间,于是表示当发送方没有是收到接收方的ACK之前,最多可以发送2**32字节数据
b)本图为初始连接的状态
c)假定发送方发送了2**32字节数据后,尚未收到接收方的任何ACK,于是发送方开始等待,也即snd.una=1(不失一般性,假定SYN分段序号为0)
d)因为已经发送了2**32字节数据,所以下一次发送的分段的序号将从0重新开始,并且我们假定在某个时刻发送方收到了接收方发回的ACK,该ACK确认了头1000个字节的数据,并且1001开始的部分分段可能正在途中(被delayed),我们不妨假定正好1001-2000的分段被延迟了,尚未到达接收方
e)因为发送方收到了ACK1000,于是发送方可以继续发送新的分段,这里一定要注意,发送方可以发送的新的数据量并不是只限1-1000这1千字节,因为究竟可以发送多大数据量取决于此ACK1000种所携带的窗口大小,而这个窗口大小又取决于接收方应用读取接收方TCP缓存的大小,显然这个读取的大小并不固定为1000字节,其实该大小和1000没有任何关系,完全是接收方应用的read行为,所以此时完全存在接收方应用所读的数据大于1000字节的情况,于是这个窗口的大小随ACK1000返回给发送方后,发送方此时就可能发送1001-2000开始的序号分段,那么问题就出现了,当新的1001分段发出后,将和老的1001分段序号完全相同,则接收方将如何判断哪个是old,哪个是new?另外,如果此时发送方因为超时而重发1-1000分段,那么同样因为接收方当前正准备接收1-1000分段,这样也造成了混淆,于是我们知道滑动窗口不可能和序号空间一样大

2、因为滑动窗口的大小必须是2的整数次幂,所以我们接下来分析是否可以为2**31

参考图2

下载 (16.87 KB)
2010-01-20 12:11





a)上图显示了当滑动窗口为32bit序号空间一半的情况
b)同理按照刚才的分析,此时发送方在未收到接收方的ACK时,最多可以发送2**31字节数据,于是snd.una=1,此时发送方暂停发送
c)并且我们假定接收方全部正确收到了,于是我们有rcv.wup=2**31,接收窗口右边界=rcv.wup+rcv.wnd
d)因为发送方一直没有收到ACK,所以此时发送方可能超时然后重发认为丢失的那些分段,这些分段的序号从0-2**31-1不妨设为seq=0,那么问题就产生了,这个seq=0的分段对于接收方而言到底是old还是new?
e)显然接收方无法正确判断!!!注意,这里的关键问题都是因为下一个要接收的正确分段序号已经开始回绕了,导致重发的分段和新发分段的序号可能重叠,所以解决问题的办法只有是想法让下一个准备接收的分段序号不和重发的分段重叠,或者反过来即便要重叠,则必须通过PAWS那样引入另外的参数来区别


3、既然2**31不能为滑动窗口的最大值,那么只能考虑2**30了,于是


参考图3
下载 (15.7 KB)
2010-01-20 12:11

接前面的分析,因为滑动窗口的大小只能是2的整数次幂,所以目前只能为2**30,这样,我们就很自然得到下面这个结论
rcv.wup+rcv.wnd – snd.una <= 2**31
此时因为下一个即将接收的分段序号没有到达32bit的最大值,所以不会出现回绕,则发送方此时只能是重发1-2**30内的序号分段,那么此时接收方就能轻易判断出此时发来的分段都是重复的old分段


通过上面分析,我们就可以自然得出window scale option的因子最大只能是14bit了


再次感谢eexplorer的热心帮助!





[ 本帖最后由 jiufei19 于 2010-1-20 12:14 编辑 ]

作者: jiufei19   发布时间: 2010-01-20

好绕。
滑动窗口一定要是2的幂次方吗?

作者: hritian   发布时间: 2010-01-20

我也不想这么绕,真希望大家能指出我是否分析有误哈

作者: jiufei19   发布时间: 2010-01-20

不错,有真的研究精神。

学习。

作者: 渣渣鸟   发布时间: 2010-01-23



QUOTE:
原帖由 hritian 于 2010-1-20 22:00 发表
好绕。
滑动窗口一定要是2的幂次方吗?



应该这么说,实际用的滑动窗口的大小并不一定是2^n。但是我们是在讨论最大允许的滑动窗口的大小,因为window scale option是一个shift count,
所以最大是64K << 16 = 2^32,接下来就是64K << 15 = 2 ^31...

而jiufei19的分析已经排除了2^32, 2^31两种情况。

我前面一直没完全同意jiufei19的结论也就是和你有一样的疑问,今天突然想到了原因

作者: eexplorer   发布时间: 2010-01-23

就是说,在讨论“最大取值”这个前提下,那么根据上面的分析,32,31均不可能,则只有30才是最大可能值了

作者: jiufei19   发布时间: 2010-01-24

本帖最后由 kutong 于 2010-12-15 15:00 编辑

如果在一个MSL内发送了3.5G数据呢?接收方1G的窗口内还是可能有可能收到重发的序号为1的数据,照样分不清是新是旧.只要是快速的网络,必须引入时间戳才能解决问题.
2**31的问题参考 tcp/ip详解 卷2 24章,这些序号的比较是用补码来实现的,补码有一些很好的数学特性.

作者: kutong   发布时间: 2010-12-15