传输层协议(5):TCP 连接(下-2)

5.2.12 收到对方报文

上一小节我们讲述了 TCP 的一方等待另一方报文超时的情形,那真是望眼欲穿、黯然神伤。那么,如果没有超时,在期望时间内收到了对方的报文呢?是不是就是欢呼雀跃一下子扑进对方的怀抱呢?

来敲门的不一定都是幸福,也有可能是狼外婆。所以,TCP 收到对端的报文时,首先要做的就是合法性检查,然后再根据检查情况,决定下一步的动作。至于什么样的报文是合法的,则与当是的 TCP 的状态有关。

 

5.2.12.1 CLOSED 状态

CLOSED 状态实际上并不是 TCP 连接的一个“真实”的状态,或者说处于 CLOSED 状态的 TCP 连接并不存在。我们知道,从“物理”角度来说,所谓 TCP 连接,指的就是一块相应的 TCB 内存块。处于CLOSED 状态的 TCP 连接,其所对应的 TCB 内存块其实已经被删除了,也就是说这样的 TCP 连接,实际上并不存在——只是我们处于表达上的方便,还是称呼“处于 CLOSED 状态的 TCP 连接”而已。

TCB 内存块已经被删除了,但是 TCP 协议栈还在。所以处于 CLOSED 状态的 TCP 连接,仍然可以接收报文,也仍然可以做相应的处理,这与关闭 TCP 协议栈的情形是不同的。

CLOSED 状态的 TCP 连接,无论收到什么报文,它都会将其丢弃。如果所接收的报文,不是 RST 报文,TCP 还会回给对方1个 RST 报文(如果所接收的是 RST 报文,则只丢弃不回应)。

TCP 所回应的 RST 报文,与其所接收的报文中的 ACK 标志位有关:

(1) 如果所接收报文的 ACK 标志位为“0”,则 RST 报文的格式为:

<SEQ = 0> <AKN = SEG.SEQ + SEG.LEN> <CTL = RST, ACK>

(2) 如果所接收报文的 ACK 标志位为“1”,则 RST 报文的格式为:

<SEQ = SEG.ACK> <CTL = RST>

 

这样表达的报文格式,需要稍微解释一下,如图5-52所示:

 

 图5-52 报文格式的解释

 

图5-52解释的应该比较清楚了,笔者就不再啰嗦了。下文的报文格式,会继续采用这种方式来表达。

 

5.2.12.2 通用处理思路

除了 CLOSED 状态,其他状态的 TCP 连接处理其所接收报文,都遵循一个通用的处理思路,如图5-53所示:

 

 图5-53 TCP 通用处理思路(抽象)

 

图5-53是通用处理思路的一个抽象表达。TCP 收到1个报文时,它首先会做参数的合法性校验,然后查看该报文是否是 RST 报文。如果报文的参数合法,并且也不是 RST 报文,那么接下来就看该报文的行为是否合法。

什么叫一个报文的行为呢?就是综合该报文的标志字段及数据长度,“拟人”出该报文的行为。比如“SYN 标志 = 1,数据长度 = 0”,这个报文的行为就是“请求创建连接”。

不同状态的 TCP,它期望的所接收报文的行为是不同的。比如处于 ESTABLISHED 状态的 TCP,对方可以发送数据给它,也可以发送关闭连接的请求给它,这些都没有超出该 TCP 的期望。但是,如果对方发送了一个创建连接的请求报文,那么此行为显然超出了该 TCP 的期望。

TCP 对于所接收报文的“非法参数”、“RST 报文”、“非法行为”、“合法行为”等情形,都有对应的处理。一般来说,针对前三种情形,TCP 做了相应处理以后,最后都是将其所接收到的报文丢弃。而合法行为的报文,TCP 处理完以后,如果该报文没有包含数据,那其实也是将其丢弃;如果包含数据,TCP 会将该数据暂时存放在自己的缓存中以便交给用户。

图5-53只是一个抽象的表达,更具体的 TCP 通用处理思路,如图5-54所示:

 

图5-54 TCP 通用处理思路

 

图5-54,第一眼看起来可能会崩溃。实际上将这个图做一个归纳,就比较好理解了:图中的“2(S/A/L 是否合法)、4(S/C 是否合法)、5(优先级是否合法)”,都是属于图5-53所说的“参数是否合法”——这样一来,就比较清晰了,图5-54仍然表达的 TCP 对于所接收报文的“非法参数”、“RST 报文”、“非法行为”、“合法行为”等情形的处理流程。另外,图5-54中画了两个框“丢弃所接收的报文”,着只是为了构图的清晰,别无它意,两个方框其实应该是一个方框。

下面就让我们从图5-54中的第1个判断点开始,进一步分析 TCP 接收到报文时的通用处理思路。

 

1)ACK 判断点

图5-54与图5-53相比,最大的不同是:图5-54中多了“是否存在 ACK”的判断点,并且“处理 NO ACK”的方框是一个虚框。

“是否存在 ACK”,指的是所接收报文中的 ACK 标志是否为“1”。并不是每个报文的 ACK 都应该为“1”,比如第1次请求创建连接的 SYN 报文,其 ACK 就等于“0”。所以不能简单地将“是否存在 ACK”理解为:存在 ACK 就是合法,不存在 ACK 就是非法。

对于不需要 ACK 标志为“1”的报文,它就不需要判断“S/A/L 的合法性”,所以相对应的处理流程,就可以跳过“2(S/A/L 是否合法)”判断点,而直接进入“3(是否存在 RST)”判断点。

对于参数错误和行为错误的报文,TCP 的处理对策一般是需要给对方回应一个报文。但是这个报文的格式及相关参数的值,又与其所接收的报文中的 ACK 标志是否为“1”有关。如果将这种区分也画在图中,那图5-54将变得更加复杂,乃至不可阅读。

鉴于以上原因,图5-54将方框“处理 NO ACK”,画成1个虚框,它并不是代表一个真正的处理过程,而是想强调:在后面的处理流程中,可能都会包含“有 ACK”和“NO ACK”两个分支,只是图形不太好表达,下文会用文字补充说明。

也就说,第1个判断点“是否存在 ACK”以及其具体的处理措施,其实是融于后面其他各个判断点及处理点中介绍。

 

2)S/L  判断点

S/L 是 Sequence Number(SEQ)、Data Length(DataLen)的缩写,指的是所接收报文的序列号和数据长度(是数据长度,不是报文长度)。TCP 对于 S/L 的判断,有两个基本原则:(1)本次所接收数据的 SEQ,与以前曾经所接收数据的 SEQ  不能重复;(2)本次所接收数据的 SEQ,不能超出接收空间(稍微有一点点特列)。具体判断方法,如表5-20所示:

 

表5-20  S/L 与 接收空间的关系

接收报文

数据长度

接收空间

判断法则

> 0

0

不能接收报文

0

0

RCV.NXT = SEG.SEQ

0

> 0

RCV.NXT <= SEG.SEQ < RCV.NXT + RCV.WND

> 0

> 0

RCV.NXT <= SEG.SEQ < RCV.NXT + RCV.WND

或者

RCV.NXT <= SEG.SEQ + SEG.LEN - 1 < RCV.NXT + RCV.WND

 

第1种情形,所接收的报文的数据长度大于0并且自己的接收空间等于0(SEG.DataLen > 0 and Rcv.WND == 0)。这种情形下,这样的报文是不能接受的。这也很好理解,接收空间为0,根本也接收不了数据。 

 

 

第2种情形的接收空间等于0但是其所接收报文的数据长度也为0(SEG.DataLen == 0 and Rcv.WND == 0)。它的意思是:固然不能接收数据,但是还是能接收“控制”报文,比如单纯的 ACK 报文、SYN 报文、FIN 报文、RST 报文等等。 

 

 

是的,对方没心情说话,你就吻她。确认过眼神,你是可以吻她的人——一定要确认过眼神,不然对方会说你耍流氓。这个眼神就是:RCV.NXT = SEG.SEQ。也就说,所接收报文的 SEQ 必须要等于接收方所期望的序列号(RCV.NXT)。

第3种情形,从接收空间的角度来说,有点“大材小用”:接收空间大于0,但是所接收的报文的数据长度仍然等于0。这有点 so easy 的感觉,^_^。不过虽然是 so easy,仍然要验证序列号:RCV.NXT <= SEG.SEQ < RCV.NXT + RCV.WND。

单纯从数学公式来看,第2种情形是第3种情形的特列(第2种情形的 RCV.WND = 0),但是实际上第3种情形隐含了一个事实:只要所发送的报文的 SEQ 在对方的接收空间内(同时报文的数据长度为0),那么就可以连续发送报文。

第4种情形,表达的是接收 TCP 数据的情形(前3种情形,都无法接收 TCP 数据)。这个情形有两个校验公式。

第(1)个公式:RCV.NXT <= SEG.SEQ < RCV.NXT + RCV.WND,它代表的含义,如图5-55所示:

 

图5-55 接收数据的第1个判断公式

 

这个公式只是限制了所接收报文的 SEQ 必须位于接收空间之内,但是并没有限制所接收报文的数据长度。也就是说,所接收报文的数据的长度,有可能大于接收空间的大小。对于这种情形 TCP 认为是可以接受的,毕竟受 MTU 的限制,一个报文的数据长度即使大于接收空间,也大不了多少,TCP 具体实现时,偷偷地给接收空间放大一点即可。这方收发双方都省事。

第(2)个公式:RCV.NXT <= SEG.SEQ + SEG.LEN - 1 < RCV.NXT + RCV.WND,它代表的含义,如图5-56所示: 

 

图5-56 接收数据的第1个判断公式

 

与第(1)个公式相反,第(2)公式恰恰只判断所接收报文的数据的结尾是否落在接收空间,至于数据的开头(SEG.SEQ)即使落在接收空间之外,也没有关系。 

 

 

对于以上所描述的4种情形,如果所接收的报文不能满足对应的判断法则,那么 TCP 会做两件事情:

(1)丢弃所接收的报文

(2)如果所接收的报文是 RST 报文,则不需要再做什么。否则,会给对方回1个报文,报文格式为:

<SEQ = SND.NXT> <ACK = RCV.NXT> <CTL = ACK>

这个报文的含义就是告诉对方:你发给我的报文,它的 SEQ 应该等于 RCV.NXT。

 

3)收到 RST 报文

TCP 收到 RST 报文的处理流程,与其当是所处的状态有关。

 

CLOSED 状态

对于处于 CLOSED 状态的 TCP 连接来说,它收到了炸弹,直接丢弃该报文,然后啥也不做。因为它原本就处于 CLOSED 状态,已经退无可退。

 

LISTEN 状态

对于处于 LISTEN 状态的 TCP 连接来说,它收到了这个炸弹,镇定自若,一笑而过,丢弃该报文,并且仍然是处于 LISTEN 状态。因为此时它还处于 LISTEN 状态,还没有建立连接,也就无所谓关闭,也就不需要关闭。就像你在街头耍酷,等着哪个美女来搭讪,此时如果有个美女对你翻白眼,你会这么办?继续耍酷呗,总不至于落荒而逃吧。 

 

耍裤

 

SYN-RECEIVED 状态

如果一个连接处于 SYN-RECEIVED 状态,并且它前一个状态是 LISTEN 状态,那么它收到 RST 报文时,也是丢弃该报文,并且回到 LISTEN 状态。如果它前一个状态不是 LISTEN 状态,那么它收到 RST 报文时,就会 Abort 该连接,并且状态转移到 CLOSED。

 

可接收数据状态

可接收数据状态,指的是:ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT。处于这些状态的 TCP 连接,是可以接收数据的。此时,如果收到 RST 报文,TCP 会中断(Abort)该连接:

(1)所有的接收、发送队列,都会被清空

(2)用户调用的 SEND、RECEIVE 接口也会被中断

(3)TCP 会给用户发送1个“connection reset”信号

(4)删除对应 TCB,TCP 连接状态变成 CLOSED

 

濒临关闭状态

濒临关闭状态,指的是:CLOSING、LAST-ACK、TIME-WAIT。处于这些状态的 TCP 连接,已经不可以接收数据,只是在等待最后的关闭。比如,处于 TIME-WAIT 状态的连接,就是在等待2个“MSL”时间——只要时间一到,就关闭连接。

处于濒临关闭状态的连接,如果收到RST 报文,那是相当于提前收到关闭连接的“指令”:直接删除对应的 TCB,状态变成 CLOSED。

 

4)S/C 判断点

S/C 是 Security/Compartment 的缩写,指的是所接收报文中的 Security/Compartment 字段的值。TCP 一般要求其所接收报文中的 S/C 与自己(TCB 内存中的字段)的值完全相同,如果不同,就给对方回以1个 RST 报文。 

 

一言不合就开炮

 

TCP 所回应的 RST 报文,与其所接收的报文中的 ACK 标志位有关:

(1) 如果所接收报文的 ACK 标志位为“0”,则 RST 报文的格式为:

<SEQ = 0> <AKN = SEG.SEQ + SEG.LEN> <CTL = RST, ACK>

(2) 如果所接收报文的 ACK 标志位为“1”,则 RST 报文的格式为:

<SEQ = SEG.ACK> <CTL = RST>

 

5)优先级判断点

如果说对于 S/C 的判断,TCP 是属于一言不合就开炮的类型,那么对于优先级,TCP 则要相对复杂不少。

(1) 如果所接收报文中,ACK 标志为1,那么报文中的优先级必须与自身的值相同,否则就回以1个 RST 报文:

<SEQ = SEG.ACK> <CTL = RST>

(2) 如果所接收报文中,ACK 标志为0,那么:

(2.1) 如果报文中的优先级与自身相同,则合法;

(2.2) 如果报文中的优先级小于自己,则合法;

(2.3) 如果报文中的优先级大于自己:

(2.3.1) 如果用户允许,则也是合法,并且将对应的 TCB 中优先级的值修改为所接收报文中的值;

(2.3.2) 如果用户不允许,则是非法——回以1个 RST 报文:

<SEQ = 0> <AKN = SEG.SEQ + SEG.LEN> <CTL = RST, ACK>

 

6)行为是否违法判断点

TCP 报文的行为,指的是报文的格式,比如报文中标志的取值( ACK = 1/0、SYN = 1/0)、报文是否包含数据等等。至于报文是否违法,与 TCP 当是的状态有关。比如,假设 TCP 处于 LISTEN 状态,我们回忆一下 TCP 连接创建的基本过程,如图5-57所示:

 

图5-57 TCP 连接创建的基本过程

 

从图5-57可以看到,处于 LISTEN 状态的 TCP,它所期望接收的报文的格式是:(1) SYN = 1、ACK = 0;(2)DataLen = 0。此时,其所接收的报文的 ACK = 1,那么 TCP 就会认为它所接收的报文的行为是“非法”。

对于非法行为的报文,TCP 或者是丢弃该报文,或者是除了丢弃报文还会给对方回1个 RST 报文。具体采取何种措施,仍然与 TCP 当时所处的状态有关。

 

7)处理合法行为的报文

跨过千山和万水,所接收的报文,既不是 RST 报文,又经过了各种判断点的考验,终于来到了这一关。

此时,TCP 一般会做几件事情:

(1)根据接收报文中的相关字段值,修正自己 TCB 中的值(TCB 的解释,请参考5.2.3、5.2.7)。

(2)如果所接收的报文中包含数据,则将该数据存储到缓存中。

(3)给对方回以 ACK 报文。

 

8)通用处理思路小结

根据前面的描述,我们将 TCP 接收到报文以后,其通用处理思路,总结为图5-58所示:

 

图5-58 TCP 通用处理思路小结

 

图5-58中的第1个判断点(是否存在 ACK),前文已经说过,它的判断及处理过程,其实是融于其他判断点和处理流程中的。

我们以表格的形式,将图5-58再阐述一遍,如表5-21所示:

 

表5-21  TCP 通用处理思路小结

判断点/处理点

判断及处理流程

S/L 判断点

针对所接收报文的 SEQ、DataLen 进行判断,如果判断报文不合格,则丢弃报文,并给给对方回以1个 ACK 报文,告知对方所发送报文的 SEQ 应该等于何值(如果所接收报文是 ACK 报文,则回报文)。

收到 RST 报文

收到 RST 报文,一般是关闭或者中断连接,但是对于 LISTEN、SYN-RECEIVED 的连接,则有所例外。

S/C 判断点

针对所接收报文中的 Security/Compartment 字段的值进行判断。如果判断报文不合格,则丢弃报文,并给给对方回以1个 RST 报文。

优先级判断点

针对所接收报文中的优先级字段的值进行判断。如果判断报文不合格,则丢弃报文,并给给对方回以1个 RST 报文。

行为是否违法

针对所接收报文中的标志字段、数据长度进行判断。如果判断报文不合格,则丢弃报文,并给给对方回以1个 RST 报文。(有的状态的 TCP 连接,不回报文)

合法行为报文

如果通过以上所有关卡,则说明报文既不是 RST 报文,而且各方面也都合法。针对此种报文,TCP 会:修正自己 TCB、存储数据(如果报文中有数据)、给对方回以1个 ACK 报文、修改(迁移)自己的状态

 

介绍了通用处理思路以后,下面我们针对具体的状态,看看 TCP 是如何处理其所接收的报文的。

 

5.2.12.3 LISTEN 状态

处于 LISTEN 状态的 TCP,它心目中苦苦等候的就是对方发送“连接创建”请求报文(SYN报文),其他报文对它来说,都是“非法报文”,如图5-59所示:

 

图5-59 LISTEN 状态期望接收的报文

 

处于 LISTEN 状态的 TCP,其收到报文时的处理流程,如图5-60所示:

 

图5-60 LISTEN 状态处理流程

 

图5-60与图5-58(通用处理思路)相比,处理顺序上有所不同,但是其基本思路还是想通的。

首先,TCP 判断其所收到的报文是否是 RST 报文。如果是 RST 报文,那么直接就将其丢弃,然后继续 LISTEN。

如果不是 RST 报文,TCP 继续检查该报文中是否包含 ACK,如果包含了 ACK,那么这个报文就是非法报文,那就丢弃该报文,并给对方回以1个 RST 报文,报文格式如下:

<SEQ = SEG.AKN> <CTL = RST>

如果没有包含 ACK,TCP 继续检查该报文是否包含 SYN,如果没有包含 SYN,那么这个报文就是非法报文,那就丢弃该报文,并给对方回以1个 RST 报文,报文格式如下:

<SEQ = 0> <CTL = RST>

其实以上两个判断(对应到图5-60中“2”和“3”),就是图5-58中所说的第6个判断点“行为是否合法”。

如果报文的行为也合法,TCP 会紧接着判断 Security/Compartment(图5-60中的“4”)、优先级(图5-60中的“5”),如果它们不合法,那么 TCP 就会丢弃该报文,并给对方回以1个 RST 报文,报文格式如下:

<SEQ = 0> <CTL = RST>

当流程来到图5-60中的第“6”步,那就说明所接收的报文是“合法”的,此时 TCP 会做如下几件事:

(1)修改自己的 TCB:RCV.NXT = (SEG.SEQ + 1)、IRS = SEG.SEQ

(2)计算出 ISS(5.2.13节会讲述如何计算 ISS)

(3)给对方发送报文,报文格式为:

<SEQ = ISS> <ACK = RCV.NXT> <CTL = SYN, ACK>

(4)继续修改自己的 TCB:SND.NXT = (ISS + 1)、SND.UNA = ISS

(5)将自己的状态迁移到 SYN-RECEIVED

 

5.2.12.4 SYN-SENT 状态

处于 SYN-SENT 状态的 TCP,它心目中苦苦等候的报文,如图5-62所示:

 

图5-62 SYN-SENT 状态期望接收的报文

 

图5-62描述了两个场景。

场景1就是典型的“三次握手”中的前两次握手:A 给 B 发送了 SYN 请求以后(此时 A 处于 SYN-SENT 状态),它期望能收到 B 的 <ACK, SYN> 报文,具体请参见5.2.1节。

场景2描述的是 A 和 B 双方同时发起 SYN 请求。也就说说,对于处于 SYN-SENT 状态的 TCP 来说,它不仅可以收到 <ACK, SYN> 报文(对应场景1),也可以收到 <SYN> 报文(对应场景2)。

无论是场景1,还是场景2,TCP 收到报文的处理流程,在上半部分都是一样的,如图5-63所示:

 

图5-63 SYN-SENT 状态接收报文的处理流程(上)

 

由于处于 SYN-SENT 状态的 TCP,它既可以收到 <ACK, SYN> 报文,也可以收到 <SYN> 报文,所以图5-62以上来就判断报文中的 ACK 是否等于1。

如果 ACK 等于1,那么 TCP 就需要判断报文中的 AKN 是否合法,判断公式是:

SND.UNA <= SEG.AKN <= SND.NXT

如果 SEG.AKN 满足此公式,那么所接收的报文就是合法的,否则就是非法的,那么其处理流程就是丢弃所接收的报文,并且给对方回以1个 RST 报文(如果所接收的报文是 RST 报文,就不给对方回报文了),报文格式是:

<SEQ = SEG.ACK> <CTL = RST>

如果所接收的报文的 AKN 合法,或者所接收的报文中的 ACK = 0,那么 TCP 就检测其所接收的报文是否是 RST 报文。如果是 RST 报文,TCP 的处理流程为:

(1)如果所接收报文的 ACK = 1,那么:给用户发送信号“error:connection reset”、丢弃报文、删除 TCB、状态转移到 CLOSED。

(2)如果所接收报文的 ACK = 0,那么:丢弃报文

如果经过了以上关卡,那么就来到了图5-62中的“5”、“6”,判断“Security/Compartment”、“优先级”是否合法,如果不合法,则给对方回以1个 RST 报文,并且丢弃所接收的报文。

如果两者都合法,那么就判断所接收报文中 SYN 是否等于1。如果 SYN 不等于1,那么根据前面的描述,这也是一个非法报文,它的处理流程是:丢弃该报文。

如果 SYN = 1,就来到了图5-62中的“8”(处理 SYN)。根据5.2.1节和5.2.4节的描述,或者根据图5-61,即使处理流程来到了步骤“8”,它也是分为两个场景,如图5-63所示:

 

图5-63 SYN-SENT 状态接收报文的处理流程(下)

 

图5-63是将图5-62中的步骤“8”做了展开,请注意图5-63与图5-62两者的起点的不同:前者是“报文合法”,后者是“收到报文”

收到报文并判断是合法以后,此时需要判断报文中的 ACK 是否等于1。ACK 等于1与否,决定了 TCP 处于图5-61所描述的哪种场景。

 

1)ACK = 1

ACK = 1,就是经典的“三次握手”中的“第二次握手”。第二次握手成功以后,TCP 会做如下几件事情:

(1)修改 TCB:RCV.NXT = SEG.SEQ + 1,IRS = SEG.SEQ,SND.UNA = SEG.AKN

(2)给对方回以1个 ACK 报文,报文格式为:

<SEQ = SND.NXT> <AKN = RCV.NXT> <CTL = ACK>

(3)将状态转移到 ESTABLISHED

 

2)ACK = 0

ACK = 0,就是“双方同时发送 SYN 请求”的场景:给对方发送 SYN 的请求后,也收到了对方 SYN 请求。TCP 会做如下几件事情:

(1)给对方回以1个 SYN/ACK 报文,报文格式为:

<SEQ = ISS> <AKN = RCV.NXT> <CTL = SYN, ACK>

(2)状态转移到 SYN-RECEIVED

 

5.2.12.5 SYN-RECEIVED 及其以后状态

SYN-RECEIVED 及其以后状态,是指 TCP 处于如下状态:SYN-RECEIVED、ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT。

处于这些状态的 TCP,收到报文时,它的处理流程如图5-64所示:

 

图5-64 SYN-RECEIVED 及其以后状态接收报文的处理流程

 

图5-64与图5-58(通用处理思路)完全相同,而且它们背后所代表的含义也完全相同。但是,与通用处理思路不同的是,这里还需要就非法报文、合法报文的处理,展开描述。

 

1)非法报文的处理

处于 SYN-RECEIVED 及其以后状态的 TCP,什么样的报文对它来说是非法的,对于非法的报文,它又该如何处理,这仍然与其状态有关,如表5-22所示:

 

表5-22  非法报文的处理

状态

非法报文

非法报文处理

SYN-RECEIVED

及其以后

SYN = 1

(1)给对端发送 RST 报文

(2)所有排队待处理的 SEND、RECEIVE 接口,都返回提示信息"connection reset"

(3)所有排队待发送或重发的数据,都清空(不再发送)

(4)给用户发送"connection reset"信号

(5)丢弃所接收的报文

(6)删除 TCB,进入 CLOSED 状态

SYN-RECEIVED

及其以后

ACK = 0

丢弃所接收的报文

CLOSE-WAIT

CLOSING

LAST-ACK

TIME-WAIT

DataLen > 0

丢弃所接收的数据

 

表5-22的第2行,从 TCP 基本思想来说,SYN-RECEIVED 及其以后的状态,其所接收的报文必须有 ACK 标志(ACK = 1),否则所接收的报文就是报文。

表5-22的第3行的含义是:处于这些状态的 TCP,其对方是不应该再发送数据了。如果所接收的报文中包含数据(DataLen > 0),那么该数据就是非法数据。

无论是非法报文还是非法数据,TCP 关于表5-22的后两行的处理,都比较温柔:仅仅是将所接受的报文(数据)做一个丢弃处理就可以了,剩下的就是保持不变,静待下一个报文的到来。

只是对于表5-22的第1行的处理,TCP 像疯了一样:关闭自己、给对方发送 RST 报文(目的是让对方也关闭)。为什么会如此疯狂,这是因为 SYN 请求背后可能隐藏着一个“可怕”的事实,如图5-65所示:

 

图5-65 SYN 报文的背后

 

图5-65中,B 在 T1 时刻,收到 A 的 SYN 请求报文以后,正常情况下,一直到双方的状态都变为 CLOSED,这中间的过程,B 都不会收到 A 发送过来的 SYN 请求报文。

但是偏偏在这个过程中,B 却收到了 A 的 SYN 报文?A 为什么会发送 SYN 报文?这是道德的沦丧还是人性的扭曲?谁知道 A 到底经历了什么?

 

 

抛开实现代码的 bug 不谈,很有可能是 A 异常重启了。重启以后,A 忘记了曾经它跟 B 所建立的 TCP 连接,并且还试图建立(新的)连接。对于 B 而言,它必须告知 A:当前它还处于它们曾经所创建的连接之中,它们之间必须要将曾经的连接了断之后,才能重新开始。这也就是表5-22的第1行的“疯狂”举动的背后含义。

表5-22所描述的非法报文的处理,可以用图5-66重新表述:

 

图5-66 非法报文的处理

 

图5-66中的“合法报文处理”,是一个虚框,这是因为其处理流程与报文的具体内容相关,也与 TCP 当时所处状态相关。下面我们就逐一讲述。

 

2)合法的单纯的 ACK 报文

前文说过,处于 SYN-RECEIVED 及其以后状态的 TCP,它所收到的报文必须包含 ACK(ACK = 1)。不过 ACK 报文,可能是单纯的 ACK 报文,也有可能叠加其他标志位(比如 FIN),也有可能叠加传输数据(即 DataLen > 0)。为了简化描述,我们将这种耦合分解,本小节只讲述“单纯的 ACK 报文”这一情形,也就是说:报文中的标志位,只有 ACK = 1,其他都等于0,并且数据长度也为0。

世界上没有无缘无故的爱,也没有无缘无故的恨,单纯的 ACK 报文,与上下文相关,如图5-67~70所示:

 

图5-67 单纯的ACK(1)

 

 

图5-68 单纯的 ACK(2)

 

 图5-69 单纯的 ACK(3)

 

 

图5-70 单纯的ACK(4)

 

图5--67~70所描述的,其实是4种典型场景,如表5-23所示:

 

表5-23  单纯 ACK 的场景及处理

图序号

场景

处理流程

5-67

“三次握手”之第3次握手

状态迁移:SYN-RECEIVED -> ESTABLISHED

5-68

“四次挥手”之第2次挥手

状态迁移:FIN-WAIT-1 -> FIN-WAIT-2

5-69

“四次挥手”之第4次挥手

状态迁移:LAST-ACK -> CLOSED

5-70

数据接收确认

下文描述

 

表5-23的前3行,以及图5-67~69,我们已经讲述了很多,这里就不再重复。

表5-23的第4行(也即图5-70),表达的是“A 给 B 发送数据以后,B 给 A 发送 ACK 报文,确认它收到了数据”这一场景。这一场景中,TCP 可以发送数据的状态不止一个(ESTABLISHED、CLOSE-WAIT),可以接收数据的状态也不止一个(ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2),所以图5-70中,将 A 和 B 的状态分别用“X、Y”表示。同时,图5-70还表明,这种场景下,TCP 的状态并不会迁移。

接收数据的确认(表5-23的第4行),与 TCP 的发送空间有关,如图5-71所示:

 

图5-71 ACK 报文的 AKN 与 TCP 的发送空间

 

图5-71中的发送空间,是接收本次报文前的发送空间。接收到本次报文以后,TCP 首先要关心的是这次所接收的 ACK 报文,是否确认了一些“已发送待确认”的数据。当然,关心归关心,具体是否能够确认,还与其所接收报文的 AKN(SEG.AKN)的值有关,如表5-24所示:

 

表5-24  ACK 报文的 AKN 与 TCP 的发送空间

SEG.AKN

说明

处理流程

SEG.AKN < SND.UNA

SEG.AKN 落在图5-71的“A”范围,那说明对方是重复确认

忽略掉这个 ACK 报文

SND.UNA < SEG.AKN <= SND.NXT

SEG.AKN 落在图5-71的“B”范围,那说明这个数据确认正是其所期望的

(1)将 SND.UNA 修改为 SEG.AKN,即:SND.UNA = SEG.AKN;

(2)将已经确认的数据,从“已发送待确认”的缓存中清除;

(3)给调用“SEND”接口的用户返回“OK”

SEG.AKN > SND.NXT

SEG.AKN 落在图5-71的“C”范围,那说明对方的确认序号出现了混乱

给对方回以1个 ACK 报文,告知自己的当前的 SEQ,报文格式为:

<SEQ = SND.NXT> <CTL = ACK>

那意思是:我当前才发送到 SND.NXT,你竟然已经确认了它后面的数据?

 

表5-24的第3行,除了解释了“SEG.AKN > SDN.NXT”时,TCP 该怎么做,同时也凑巧引出了单纯发送 ACK 报文的第5种场景,^_^

我们再回到第4种场景(数据接收确认)上来,处理完 SEG.AKN 与自己接收空间的 SND.UNA 之间的关系以后,TCP 还会修改自己的 SND.WND。仍然是如图5-71所示,只要是“SND.UNA < SEG.AKN <= SND.NXT”,就有可能需要修改自己的 SND.WND:SND.WND = SEG.WND。

但是这里强调的是“有可能需要”,而不是“绝对需要”。这是因为担心出现“先发后至”的场景,如图5-72所示:

 

图5-72 后发先至

 

图5-72中,如果“报文1”比“报文2”早到,那么 B 的 SND.WND 就会被设置为正确的值(2000)。但是,却发生了“先发后至”的情形:“报文1”比“报文2”晚到,结果造成了 B 的 SND.WND 被设置成了错误的值(1000)。为了解决这个情况,TCP 引入了两个变量:SND.WL1、SND.WL2。(SND.WL 是 SEND.Window LAST 的缩写)我们先不管 SND.WL1、SND.WL2 的含义,就把它们当作2个变量,初始值都为0。

TCP 关于 SND.WND 是否修改的判断规则,如表5-25所示:

 

表5-25  TCP 关于 SND.WND 是否修改的判断规则

序号

条件

1

SND.WL1 < SEG.SEQ

2

(SND.WL1 == SEG.SEQ) and (SND.WL2 <= SEG.ACK)

 

表5-25中的两个条件,只要有1个条件成立,那么赋值如下:

SND.WND = SEG.WND

SND.WL1 = SEG.SEQ

SND.WL2 = SEG.ACK

通过这个赋值,我们知道:TCP 的发送窗口(SND.WND)被修改了(赋值为本次接收报文中的 Window 字段的值),SND.WL1、SND.WL2 两个变量的值也被修改了。

如果我们把这个修改称为“发送窗口修改点(简称:修改点)”的话,表5-25的条件1,其实可以这样等价:

上次修改点时所接收报文的 SEQ < 本次所接收报文的 SEQ

这句话的含义就是:本次所接收报文是“新”报文,没有发生“先发后至”的情形。

同理,表5-25的条件2,也可以这样理解:

即使“上次修改点时所接收报文的 SEQ 等于本次所接收报文的 SEQ”,但是“上次修改点时所接收报文的 AKN 小于本次所接收报文的 SEQ”,所以本次所接收报文是“新”报文,没有发生“先发后至”的情形。

唉......TCP 真实考虑周全,把这样的规则,用文字表达出来,鄙人写起来累,您读起来也累。但愿我写的不是太苦涩,能您看的懂,^_^

总之吧,我们总结一下表5-23所说的第4种场景(数据接收确认):

(1)如果所接收报文的 AKN 在已发送待确认的序列号之间,那么修改发送空间的 SEND.UNA 变量:SND.UNA = SEG.AKN

(2)同时,如果这个报文确实是最“新”的,那么也修改 SND.WND、SND.WL1、SND.WL2 这3个变量:SND.WND = SEG.WND, SND.WL1 = SEG.SEQ, SND.WL2 = SEG.ACK

(3)其余细节,请参见前文描述

 

最后补充一下 RFC 793 关于 SND.WL1、SND.WL2 的解释:

SND.WL1:segment sequence number used for last window update

SND.WL2:segment acknowledgment number used for last window update

 

3)合法的 FIN 报文

RFC 793 定义了6个标志位、RFC 3168 增加了2个、RFC 3540 增加了1个,一共9个。到目前为止,我们介绍了其中4个:ACK、RST、SYN、FIN,其余的标志位需要放到后面的章节介绍。所以本小节介绍的 FIN 报文(FIN = 1),是本章所介绍的最后1个“控制”报文。

收到 FIN 报文后的处理情况,也与 TCP 当时所处的状态相关。

对于 CLOSED、LISTEN、SYN-SENT 状态的 TCP 来说,如果它收到 FIN 报文,会直接丢弃该报文,然后就不再做其他任何处理了。

对于 CLOSED 状态而言,这似乎有点矛盾,但是 RFC 793 确实是这么描述的:

If the state is CLOSED (i.e., TCB does not exist) then ......An incoming segment not containing a RST causes a RST to be sent in response.(处于 CLOSED 状态的 TCP,如果收到1个非 RST 报文,会给对方回以1个 RST 报文)

Do not process the FIN if the state is CLOSED, LISTEN or SYN-SENT since the SEG.SEQ cannot be validated; drop the segment and return.(处于 CLOSED、LISTEN、SYN-SENT 状态的 TCP,不处理 FIN 报文,直接丢弃,然后返回)

我们不纠结这个矛盾,笔者以为,处于 CLOSED 状态的 TCP,收到 FIN 报文,以上两种处理方法都可以接受。

 

对于处于 SYN-RECEIVED 及其以后状态的 TCP 来说,它所收到的合法的 FIN 报文一定会包含 ACK(ACK = 1),所以此时 TCP 首先会做出上一小节“合法的单纯的 ACK 报文”所讲述的那些处理流程,然后再来处理 FIN 本身。具体处理流程,与 TCP 当时所处状态有关,如表5-26所示:

 

表5-26  处理 FIN 报文

状态

处理流程

SYN-RECEIVED        ESTABLISHED

直接状态迁移到 CLOSE-WAIT

FIN-WAIT-1

如果它曾经的 FIN 被对方 ACK 了,那么状态迁移到 TIME-WAIT,同时开启 time-wait 定时器;否则状态迁移到 CLOSING

FIN-WAIT-2

状态迁移到 TIME-WAIT,同时开启 time-wait 定时器

CLOSE-WAIT

仍然保持 CLOSE-WAIT 状态。(潜台词就是:收到多次 FIN 报文没有关系,反正状态保持不变就可以了)

CLOSING

仍然保持 CLOSING 状态

LAST-ACK

仍然保持 LAST-ACK 状态

TIME-WAIT

仍然保持 TIME-WAIT 状态,并重新开始 2 MSL time-wait 计时

 

4)合法的数据

终于来到激动人心的时刻,开始接收数据了。TCP 如果不能接收数据,那跟咸鱼有什么区别?

前文说过,处于 CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT 状态的 TCP,不应该能收到数据(不是本方不能接收数据,而是对方不应该发送数据),所以此时 TCP 如果收到的报文中包含数据,它会忽略这些数据。

处于 ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2 状态的 TCP,则可以接收数据,只要承载这些数据的报文,经过了前面层层的检验。

承载合法数据的报文,可能会包含 ACK(ACK = 1),所以此时 TCP 首先会做出上一小节“合法的单纯的 ACK 报文”所讲述的那些处理流程,然后再来处理数据本身。

经过了那么多铺垫,TCP 处理所接收的数据,就变得比较简单了:

(1)将数据传递(deliver)给用户缓存

(2)修改 TCB:

(A)RCV.NXT = SEG.SEQ + SEG.DataLen,

(B)RCV.WND = RCV.WND(数据已经传递到用户缓存)or RCV.WND = RCV.WND - SEG.DataLen(数据尚未传递到用户缓存)

(3)给对方回以 ACK 报文,报文格式为:

<SEQ = SND.NXT> <AKN = RCV.NXT, WND = RCV.WND> <CTL = ACK>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值