- TCP详解
★★★5.4 TCP的流量控制
★★★TCP的流量控制
流量控制(flow control)就是让发送方的发送速率不要太快,要让接收方来得及接收。
利用滑动窗口机制可以很方便的在 TCP 连接上实现对发送方的流量控制。
如下图所示,主机 A 和主机 B 已经建立了 TCP 连接,A 给 B 发送数据,B 对 A 进行流量控制。
图中是主机 A 中待发送数据的字节序号,假设主机 A 发送的每个 TCP 数据报文段可携带 100 字节数据,因此图中每个小格子表示 100 个字节数据的序号,在主机 A 和 B 建立 TCP 连接时,B告诉A:“我的接收窗口为400“,则主机 A 将自己的发送窗口也设置为 400,这意味着主机 A 在未收到主机 B 发来的确认时,可将序号落入发送窗口中的全部数据发送出去;
接下来说明主机 B 对 A 的流量控制,主机 A 将发送窗口内序号 1 ~ 100 的数据封装成一个 TCP 报文段发送出去,发送窗口内还有 300 字节可以发送,这里的 seq 是 TCP 报文段首部中的序号字段,取值 1 表示 TCP 报文段数据载荷的第一个字节的序号是 1,这里的 DATA 表示这是 TCP 数据报文段;主机 A 将发送窗口内序号 101 ~ 200 的数据封装成一个 TCP 报文段发送出去,发送窗口内还有 200 字节可以发送,seq = 101;主机 A 将发送窗口内序号 201 ~ 300 的数据封装成一个 TCP 报文段发送出去,但该报文段在传输的过程中丢失了,发送窗口内还有 100 字节可以发送。
主机 B 对主机 A 发送的 201 号以前的数据进行累计确认,并在该累计确认中将窗口字段的值调整为 300,即对主机 A 进行流量控制。这里的大写 ACK 是 TCP 报文段首部中的标志位,取值 1 表示这是一个 TCP 确认报文段;小写 ack 是 TCP 报文段首部中的确认号字段,取值 201 表示序号 201 之前(不包括 201 )的数据已全部确接收;rwnd(Receiver Window,接收窗口) 是 TCP 报文段首部中的窗口字段,取值 300 表示自己的接收窗口大小为 300。

主机 A 收到该累计确认后,将发送窗口向前滑动,使已发送并收到确认的这些数据的序号移出发送窗口,由于主机 B 在该累计确认中将自己的接收窗口调整为 300,因此,主机 A 相应地将自己的发送窗口调整为 300。目前,主机 A 发送窗口内的序号为 201 ~ 500,即主机 A 可以发送 300 字节数据。由于 201 ~ 300 号字节的数据发送但丢失了,所以主机 A 并没有收到对应于这些数据的确认分组 ack,所以等到重传计时器超时,它们就会被重传。301 ~ 400 号字节以及 401 ~ 500 号字节还未被发送,可被分别封装在一个 TCP 报文段中发送。主机 A 现在可将发送缓存中序号 1 ~ 200 的字节数据全部删除了。

主机 A 将发送窗口内序号 301 ~ 400 的数据封装成一个 TCP 报文段发送出去,发送窗口内还有 100 字节可以发送,seq = 301;主机 A 将发送窗口内序号 401 ~ 500 的数据封装成一个 TCP 报文段发送出去,则此时序号落在发送窗口内的数据已经全部发送出去了,不能再发送新数据了,seq = 401;然后发送窗口内序号 201 ~ 300 这 100 个字节数据的重传计时器超时了,主机 A 将它们重新封装成一个 TCP 报文段发送出去,seq = 201。

主机 B 对主机 A 发送的 501 号以前的数据进行累计确认,并在该累计确认中将窗口字段的值调整为 100,即对主机 A 进行第二次流量控制。主机 A 收到该累计确认后,将发送窗口向前滑动,使已发送并收到确认的这些数据的序号移出发送窗口,由于主机 B 在该累计确认中将自己的接收窗口调整为 100,因此,主机 A 相应地将自己的发送窗口调整为 100。目前,主机 A 发送窗口内的序号为 501 ~ 600,即主机 A 可以发送 100 字节数据。主机 A 现在可将发送缓存中序号 201 ~ 500 的字节数据全部删除了。

A 将发送窗口内序号 501 ~ 600 的数据封装成一个 TCP 报文段发送出去,则此时序号落在发送窗口内的数据已经全部发送出去了,不能再发送新数据了,seq = 501;主机 B 对主机 A 发送的 601 号以前的数据进行累计确认,并在该累计确认中将窗口字段的值调整为 0,即对主机 A 进行第三次流量控制。主机 A 收到该累计确认后,将发送窗口向前滑动,使已发送并收到确认的这些数据的序号移出发送窗口,由于主机 B 在该累计确认中将自己的接收窗口调整为 0,因此,主机 A 相应地将自己的发送窗口调整为 0,则此时主机 A 已经不能再发送一般的 TCP 报文段了。

主机 A 现在可将发送缓存中序号 501 ~ 600 的字节数据全部删除了。假设主机 B 向主机 A 发送了零窗口的报文段后不久,主机 B 的接收缓存又有了一些存储空间,于是,主机 B 向主机 A 发送了接收窗口等于 300 的报文段,然而,这个报文段在传输过程中丢失了,主机 A 一直等待主机 B 发送的零窗口的通知,而主机 B 也一直等待主机 A 发送的数据,如果不采取措施,这种互相等待而形成的死锁局面将一直持续下去。

持续计时器
为了解决上面的问题,TCP 为每一个连接设有一个持续计时器,只要 TCP 连接的一方收到对方的零窗口通知,就要启动持续计时器,若持续计时器超时,就发送一个零窗口探测报文(仅携带一字节的数据),而对方在确认这个探测报文段时,给出自己现在的接收窗口值:如果接收窗口仍然为 0,那么收到这个报文段的一方就重新启动持续计时器;如果接收窗口不是 0,则可以解决死锁的问题。

若零窗口探测报文段在传输过程中丢失了,也可以解决死锁问题,因为零窗口探测报文段也有重传计时器,当重传计时器超时后,零窗口探测报文段会被重传。
★★★5.5 TCP的拥塞控制
拥塞
在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就要变坏,这种情况就叫做拥塞(congestion)。
若出现拥塞而不进行控制,整个网络的吞吐量将随输入负荷的增大而下降。

应尽可能使实际的拥塞控制曲线接近于理想的拥塞控制曲线。
★拥塞窗口
拥塞窗口 cwnd(Congestion Window),是发送方维护的一个状态变量,其值取决于网络的拥塞程度,并且动态变化。
拥塞窗口 cwnd 的维护原则:只要网络没有出现拥塞,拥塞窗口就再增大一些;但只要网络出现拥塞,拥塞窗口就减少一些。
判断出现网络拥塞的依据:没有按时收到应当到达的确认报文(即发生超时重传)。
拥塞窗口会随着网络的拥塞程度以及所使用的拥塞控制算法动态变化。

拥塞窗口 cwnd;发送窗口 swnd;接收窗口 rwnd。
★★★TCP的四种拥塞控制算法

慢开始算法
慢开始算法(slow-start):发送方每收到一个对新报文段的确认时,就把拥塞窗口值加 1,然后开始下一轮的传输。
如下图所示,在 TCP 双方建立逻辑连接关系时,拥塞窗口的初始值被设置为 1(发送窗口值 = 拥塞窗口值,所以发送窗口值也等于 1),慢开始门限 ssthresh 的初始值被设置为 16。
在执行慢开始算法时,发送方每收到一个对新报文段的确认时,就把拥塞窗口值加 1,然后开始下一轮的传输。当拥塞窗口值增长到慢开始门限 ssthresh 时,就改为执行拥塞避免算法。

传输轮次:发送方给接收方发送数据报文段后,接收方给发送方发回相应的确认报文段的一整个过程。
如下图所示,发送方给接收方发送 0 号 TCP 数据报文段,接收方收到后,给发送方发回对 0 号报文段的确认报文段,发送方收到该确认报文段后,就将 cwnd 的值加 1,此时,发送方就可以发送 1 ~ 2 号 共 2 个TCP 数据报文段。

同理,接收方收到后,给发送方发回对 1 ~ 2 号报文段的确认报文段,发送方收到该确认报文段后,就将 cwnd 的值加 2,此时,发送方就可以发送 3 ~ 6 号 共 4 个TCP 数据报文段。

当发送方的 cwnd 的值增大到 16 时,就要从慢开始算法转变到拥塞避免算法。

拥塞避免算法
拥塞避免算法(congestion avoidance):每个传输轮次结束后,拥塞窗口值只能线性加 1。
如下图所示,发送方现在可以发送 15 ~ 30 号 共 16 个TCP 数据报文段,接收方收到后,给发送方发回对 15 ~ 30 号报文段的确认报文段,由于此时采用的是拥塞避免算法,所以发送方收到该确认报文段后,就将 cwnd 的值加 1,此时,发送方就可以发送 31 ~ 47 号 共 17 个TCP 数据报文段。

同理,接收方收到后,给发送方发回对 31 ~ 47 号报文段的确认报文段,发送方收到该确认报文段后,就将 cwnd 的值加 1,此时,发送方就可以发送 48 ~ 65 号 共 18 个TCP 数据报文段。

随着传输轮次的增加,由于采用了拥塞避免算法,所以拥塞窗口 cwnd 的值都线性加 1。当 cwnd 增加到 24 时,发送方可以发送 171 ~ 194 号 共 24 个TCP 数据报文段,假设这 24 个数据报文段在传输过程中丢失了几个,这必然会造成发送方对这些丢失报文段的超时重传,发送方以此判断网络很可能出现了拥塞,需要进行以下工作:
将 ssthresh 的值更新为发生拥塞时 cwnd 值的一半;
将拥塞窗口 cwnd 的值减小为 1,并重新开始执行慢开始算法。

依次类推,循环往复。

慢开始和拥塞避免算法总结如下:

快重传和快恢复算法对慢开始和拥塞避免算法的改进。

快重传算法
快重传算法(fast retransmit):使发送方尽快进行重传,而不是等超时重传计时器超时再重传。
如下图所示,发送方发送 1 号数据报文段,接收方收到后给发送方发回对 1 号报文段的确认报文段;
在该确认报文段到达发送方之前,发送方还可以将发送窗口内的 2 号数据报文段发送出去,接收方收到后给发送方发回对 2 号报文段的确认报文段;
在该确认报文段到达发送方之前,发送方还可以将发送窗口内的 3 号数据报文段发送出去,但该报文段丢失了,接收自然不会给发送方发回针对该报文段的确认报文段;
发送方还可以将发送窗口内的 4 号数据报文段发送出去,接收方收到后发现这不是按序到达的确认报文段,因此给发送方发回针对 2 号数据报文段的重复确认报文段,表明 ”我现在希望收到的是 3 号报文段,但是我没有收到 3 号报文段,而是收到了未按序到达的报文段“;
发送方还可以将发送窗口内的 5 号数据报文段发送出去,接收方收到后发现这不是按序到达的确认报文段,因此给发送方发回针对 2 号数据报文段的重复确认报文段;
发送方还可以将发送窗口内的 6 号数据报文段发送出去,接收方收到后发现这不是按序到达的确认报文段,因此给发送方发回针对 2 号数据报文段的重复确认报文段。
至此,发送方收到了 3 个连续的、对 2 号报文段的重复确认,则发送方立即重传 3 号数据报文段,接收方收到该报文段后,给发送方发回针对 6 号报文段的确认报文段,表明序号到 6 为止的报文段都被正确接收了,这样就不会造成对 3 号报文段的超时重传。即对于个别丢失的报文段,发送方不会出现超时重传,也就不会误认为出现了拥塞(进而减小拥塞窗口 cwnd 的值到 1 ),使用快重传可以使整个网络的吞吐量提高约 20%。

快恢复算法

四种算法综合举例

- 注意:慢开始和拥塞避免算法配套使用;快重传和快恢复算法配套使用。

- 注意:由于出现了 ”超时“,所以是慢开始和拥塞避免算法,而不是快重传和快恢复算法,因为如果要使用快重传和快恢复算法,并不是 ”超时“ 再使用,而是收到三个连续的重复确认才使用。(快重传和快恢复算法的目的是不要因为出现 ”误导” 的 ”超时“ 而将 cwnd 的值重置为 1,导致传输效率的降低,所以若采用了快重传和快恢复算法,是不会出现 “超时” 的)
5.6 TCP超时重传时间的选择
超时重传时间RTO的选择
如下图所示,是已经建立 TCP 连接的主机 A 和主机 B,主机 A 给主机 B 发送 0 号 TCP 数据报文段并接收到主机 B 的针对该报文段的确认报文段所用的时间为第 0 号报文段的往返时间 RTT0(Round-Trip Time)。
当超时重传时间 RTO(Retransmission TimeOut)设置的比 RTT0 小时,会引起报文段的不必要重传,使网络负荷增大;

当超时重传时间 RTO(Retransmission TimeOut)设置的远比 RTT0 大时,会使重传推迟的时间太长,使网络的空闲时间增大,降低了传输效率。

因此,超时重传时间 RTO 的值应该设置为略大于报文段往返时间 RTT 的值。
由于 TCP 下层是复杂的互联网环境,所以每个报文段的往返时间 RTT 都不一定相同,如下图,RTT1 就远大于 RTT0,因此,RTO 的值的设置不能直接使用某次测量得到的 RTT 样本来计算。


超时重传时间 RTO 的值应略大于加权平均往返时间 RTTs,计算公式如下:

往返时间RTT的测量
往返时间 RTT 的测量比较复杂,下面两种情况都会导致 RTT 测量的偏差,进而导致计算 RTO 的偏差:

针对上述问题,对 Karn 算法进行修正,即报文段每重传一次,就把超时重传时间 RTO 增大一些,典型的做法是将新 RTO 的值取为旧 RTO 值的 2 倍。

RTO 计算的例子:

★★★5.7 TCP可靠传输的实现
★TCP可靠传输的实现过程
TCP 基于以字节为单位的滑动窗口来实现可靠传输。
如下图所示,发送方和接收方已经建立了 TCP 连接,发送方给接收方发送 TCP 数据报文段,接收方给发送方发送相应的 TCP 确认报文段,图中还显示了发送方待发送数据字节的一部分序号。

假设此时发送方接收到了一个来自接收方的确认报文段,其中
rwnd = 20; ack = 31
,表明此时接收方的接收窗口的大小为 20,接收方希望收到下一个数据的序号是 31(即序号到 30 为止的数据已经被全部正确接收了)。
发送窗口
发送方可以根据这两个字段的值构造出自己的发送窗口(忽略了拥塞情况)。

于是发送方可以把发送窗口内的数据依次全部发送出去,凡是已经发送出去的数据,在未收到相应的确认报文段之前,都必须暂时保留,以便在超时重传时使用。

发送窗口前沿和后沿的几种移动情况:

假设此时发送方将发送窗口内 31 ~ 41 号数据封装在几个不同的报文段中发送出去,则发送窗口内序号 31 ~ 41 的数据已经发送但未收到确认,而序号 42 ~ 50 的数据是允许发送但还未发送的。
对于滑动窗口的状态,可以用 P1、P2、P3 三个指针来进行描述和维护,如下图所示,小于 P1 的就是已发送并已收到确认的部分;大于等于 P3 的是不允许发送的部分;P3 - P1 = 发送窗口的尺寸;P2 - P1 = 已发送但尚未收到确认的字节数;P3 - P2 = 允许发送但当前尚未发送的字节数量(又称为可用窗口或有效窗口)。

接收窗口
如下图所示,由前面的
rwnd = 20
可知,接收窗口的尺寸为 20,序号到 30 为止的数据是已经发送过相应确认并已交付给应用进程的数据,因此无需再保留这些数据,可将它们从接收缓存中删除;接收窗口内 31 ~ 50 号数据是允许接收的数据;接收窗口外 51 号及其后续数据,目前不允许接收。
假设发送方之前发送的封装有 32 和 33 号数据的报文段到达了接收方,由于数据序号落在接收窗口内,所以接收方接收它们,并将它们存入接收缓存,但是它们是未按序到达的数据(31 号数据还未到达),由于接收方只能对按序收到的数据中的最高序号给出确认,因此此时接收方的确认报文段中的确认序号仍为 31(即希望收到 31 号数据),而
rwnd = 20
保持不变,即此时的确认报文段中rwnd = 20; ack = 31
,发送方收到该确认报文段后,发现这是一个针对 31 号数据的重复确认,就知道了接收方收到了未按序到达的数据,由于这是针对 31 号数据的第一个重复确认,因此这并不会引起发送方针对该数据的快重传。
现在假设封装有 31 号数据的报文段到达了接收方,接收方接收该报文段,将其封装的 31 号数据存入接收缓存,接收方现在可将接收到的 31 ~ 33 号数据交付给应用进程,然后将接收窗口向前移动 3 个序号,并给发送方发送确认报文段,若接收窗口的大小不变,则此时该确认报文段中
rwnd = 20; ack = 34
,ack = 34 表明接收方已经收到了到 33 号数据为止的全部数据。
现在,假设又有几个数据报文段到达了接收方,它们封装有 37、38、40 号数据,这些数据的序号虽然落在接收窗口内,但都是未按序到达的数据,只能先暂存在接收缓存中。假设接收方先前发送的确认报文段到达了发送方,发送方接收后,将发送窗口向前滑动 3 个序号,且可以将 31 ~ 33 号数据从发送缓存中删除。

发送方继续将发送窗口内序号 42 ~ 53 的数据封装在几个不同的报文段中发送出去,则此时发送窗口内的序号已经用完了,发送方在未收到接收方发来确认的情况下,不能再发送新的数据。序号落在发送窗口内的已发送数据,如果迟迟不能收到接收方的确认,就会产生超时重传。

注意事项

★★★5.8 TCP的运输连接管理
TCP 是面向连接的协议,它基于运输连接来传送 TCP 报文段。
TCP 运输连接的建立和释放是每一次面向连接的通信中必不可少的过程。
TCP 运输连接有以下三个阶段:
- 建立 TCP 连接(通过 “三报文握手” );
- 基于已建立的 TCP 连接进行可靠的数据传送;
- 释放 TCP 连接(通过 “四报文挥手” )。
TCP 的运输连接管理就是使运输连接的建立和释放都能正常地进行。

★5.8.1 TCP的连接建立
- TCP 连接的建立需要解决以下三个问题:
- 使 TCP 双方能够确知对方的存在;
- 使 TCP 双方能够协商一些参数(如最大窗口值、是否使用窗口扩大选项和时间戳选项以及服务质量等);
- 使 TCP 双方能够对运输实体资源(如缓存大小、连接表中的项目等)进行分配。
★★★TCP连接建立的过程
TCP 使用 “三报文握手” 来建立连接。
如下图所示,有两台主机,其中一台主机中的某个应用进程主动发起 TCP 连接建立,称为 TCP 客户;另一台主机中被动等待 TCP 连接建立的应用进程,称为 TCP 服务器。
我们可以将 TCP 连接建立的过程比喻为 “握手”,“握手” 需要在 TCP 客户和 TCP 服务器之间交换三个 TCP 报文段。
最初,两端的 TCP 进程都处于关闭状态。
一开始,TCP 服务器进程首先创建传输控制块,用于存储 TCP 连接中的一些重要信息,如图所示,然后该进程就准备接收 TCP 客户进程的连接请求,此时,TCP 服务器进程就进入监听状态,等待 TCP 客户进程的连接请求;

由于 TCP 服务器进程是被动等待来自 TCP 客户进程的连接请求,而不是主动发起,因此称为被动打开连接。
TCP 客户进程也是首先创建传输控制块(内容同 TCP 服务器进程的传输控制块),然后,在打算建立 TCP 连接时,向 TCP 服务器进程发送 TCP 连接请求报文段,并进入同步已发送状态。TCP 连接请求报文段首部中的同步位 SYN 被设置为 1,表明这是一个 TCP 连接请求报文段;序号字段 seq 被设置为一个初始值 x,作为 TCP 客户进程所选择的初始序号。
注意:TCP 规定 SYN 被设置为 1 的报文段不能携带数据,但要消耗掉一个序号。

由于 TCP 客户进程是主动发起 TCP 连接请求的,因此称为主动打开连接。
TCP 服务器进程收到 TCP 连接请求报文段后,如果同意建立连接,则向 TCP 客户进程发送 TCP 连接请求确认报文段,并进入同步已接收状态,该报文段中的同步位 SYN 和确认位 ACK 都设置为 1,表明这是一个 TCP 连接请求确认报文段;序号字段 seq 被设置为一个初始值 y,作为 TCP 服务器进程所选择的初始序号;确认号字段 ack 的值被设置为 x+1,这是对 TCP 客户进程所选择的初始序号的确认。
注意:TCP 规定 SYN 被设置为 1 的报文段不能携带数据,但要消耗掉一个序号,因此这个 TCP 连接请求确认报文段也不能携带数据。

TCP 客户进程收到 TCP 连接请求确认报文段后,还要向 TCP 服务器进程发送一个普通的 TCP 确认报文段(该报文段是对 TCP 服务器进程发送的 TCP 连接请求确认报文段的确认),并进入连接已建立状态,该报文段首部中的 确认位 ACK 被设置为 1,表明这是一个普通的 TCP 确认报文段;序号字段 seq 被设置为 x+1,这是因为TCP 客户进程发送的第一个 TCP 报文段的序号为 x 且不携带数据,但又消耗掉一个序号,因此第二个报文段的序号为 x+1;确认号字段 ack 的值被设置为 y+1,这是对 TCP 服务器进程所选择的初始序号的确认。

注意:TCP 规定普通的 TCP 确认报文段可以携带数据,但如果不携带数据,则不消耗序号,在这种情况下(不携带数据的情况),所发送的下一个 TCP 数据报文段的序号仍是 x+1。
TCP 服务器进程收到该确认报文段后也进入连接已建立状态,现在 TCP 双方都进入了连接已建立状态,它们可以基于已建立好的 TCP 连接,进行可靠的数据传输了。

★TCP三报文握手的原因
由上所述,在 TCP 客户进程接收到 TCP 服务器进程发送的 TCP 连接请求确认报文段后,进入连接已建立状态,然后又给 TCP 服务器发送了一个普通的 TCP 确认报文段,这一步并不能省略,原因如下:
如下图所示,假设只有 “两报文握手”,TCP 客户进程向 TCP 服务器进程发送了一个 TCP 连接请求报文段,但该报文段在某些网络结点长时间滞留了,这将造成 TCP 连接请求报文段的超时重传,假设重传的报文段被 TCP 服务器进程正常接收,TCP 服务器进程给 TCP 客户进程发送一个 TCP 连接请求确认报文段,并进入连接已建立状态。
TCP 客户进程接收到 TCP 连接请求确认报文段后,进入 TCP 连接已建立状态,但不会给 TCP 服务器进程发送针对该报文段的普通确认报文段,现在,TCP 双方都处于连接已建立状态,它们可以相互传输数据,之后可以通过 “四报文挥手” 来释放连接。
TCP 双方释放连接后都进入了关闭状态。一段时间后,之前滞留在网络中的那个失效的 TCP 连接请求报文段到达了 TCP 服务器进程,TCP 服务器进程会误认为这是 TCP 客户进程又发起了一个新的 TCP 连接请求,于是给 TCP 客户进程发送一个 TCP 连接请求确认报文段,并又进入连接已建立状态。
该报文段又到达 TCP 客户进程,由于 TCP 客户进程并没有发起新的 TCP 连接请求,并且处于关闭状态,就不会理会该报文段,但 TCP 服务器进程已经进入连接已建立状态,它认为新的 TCP 连接已经建立好了,并一直等待 TCP 客户进程发来数据,这将白白浪费 TCP 服务器进程所在主机的很多资源。

因此,采用 “三报文握手” 而不是 “两报文握手” 来建立 TCP 连接,是为了防止已失效的连接请求报文突然又传送到了 TCP 服务器,因而导致错误。

★5.8.2 TCP的连接释放
★★★TCP连接释放的过程
TCP 使用 “四报文挥手” 来释放连接。
如下图所示,TCP 客户进程和 TCP 服务器进程的数据传输结束后,TCP 通信双方都可以释放连接。
现在 TCP 客户进程和 TCP 服务器进程都处于连接已建立状态,假设使用 TCP 客户进程的应用进程通知其主动关闭 TCP 连接,则 TCP 客户进程会发送 TCP 连接释放报文段,并进入终止等待 1 状态,该报文段首部中的终止位 FIN 和确认位 ACK 的值都被设置为 1,表明这是一个 TCP 连接释放报文段,同时也对之前收到的报文段进行确认;序号字段 seq 的值被设置为 u,u = TCP 客户进程之前已经传送过的、数据的最后一个字节的序号加 1;确认号字段 ack 的值被设置为 v,v = TCP 客户进程之前已经收到的、数据的最后一个字节的序号加 1。
注意:TCP 规定 FIN 被设置为 1 的报文段即使不携带数据,也要消耗掉一个序号。

TCP 服务器进程收到 TCP 连接释放报文段后,会发送一个普通的 TCP 确认报文段并进入关闭等待状态,该报文段首部中的 确认位 ACK 被设置为 1,表明这是一个普通的 TCP 确认报文段;序号字段 seq 被设置为 v,v = TCP 服务器进程之前已经传送过的、数据的最后一个字节的序号加 1,这也与之前收到的 TCP 连接释放报文段中的确认号匹配;确认号字段 ack 的值被设置为 u+1,这是对 TCP 连接释放报文段的确认。
TCP服务器进程这时应通知高层应用进程,TCP 客户进程要断开与自己的 TCP 连接,此时,从 TCP 客户进程到 TCP 服务器进程这个方向的连接就释放了,这时的 TCP 连接属于半关闭状态,也就是 TCP 客户进程已经没有数据要发送了,但 TCP 服务器进程如果还有数据要发送,TCP 客户进程仍要接收,即从 TCP 服务器进程到 TCP 客户进程这个方向的连接并未关闭。

TCP 客户进程收到 TCP 确认报文段后就进入终止等待 2 状态,等待 TCP 服务器进程发出的 TCP 连接释放报文段,若使用 TCP 服务器进程的应用进程已经没有数据要发送了,应用进程就通知其 TCP 服务器进程释放连接,由于 TCP 连接释放是由 TCP 客户进程主动发起的,因此 TCP 服务器进程对 TCP 连接的释放称为被动关闭连接。

TCP 服务器进程发送 TCP 连接释放报文段并进入最后确认状态,该报文段首部中的终止位 FIN 和确认位 ACK 的值都被设置为 1,表明这是一个 TCP 连接释放报文段,同时也是对之前收到的报文段进行确认;现在假定序号字段 seq 的值为 w,这是因为在半关闭状态下,TCP 服务器进程可能又发送了一些数据,w = TCP 服务器进程之前已经传送过的、数据的最后一个字节的序号加 1;确认号字段 ack 的值为 u+1,这是对之前收到的 TCP 连接释放报文段的重复确认(因为 TCP 客户进程已经不再发送 TCP 数据报文段给 TCP 服务器进程了,而只是发送对 TCP 服务器进程发送的 TCP 数据报文段的确认报文段,而该普通确认报文段由于并未携带数据,所以序号字段 seq 的值并不增加,seq 仍为 u)。

TCP 客户进程收到 TCP 连接释放报文段后,必须针对该报文段发送普通的 TCP 确认报文段,然后进入时间等待状态,该报文段首部中的 确认位 ACK 被设置为 1,表明这是一个普通的 TCP 确认报文段;序号字段 seq 的值被设置为 u+1,这是因为 TCP 客户进程之前发送的 TCP 连接释放报文段虽然不携带数据,但要消耗掉一个序号;确认号字段 ack 的值被设置为 w+1,这是对所收到的 TCP 连接释放报文段的确认。

TCP 服务器进程收到该确认报文段后就进入关闭状态,而 TCP 客户进程还要经过 2MSL 后才能进入关闭状态。MSL(Maximum Segment Lifetime),最长报文段寿命。

TCP 客户进程在发送完确认报文段后不直接进入关闭状态,而要进入时间等待状态,等待 2MSL 时间的原因在于:
- 可以确保 TCP 服务器进程可以收到最后一个 TCP 确认报文段而进入关闭状态;
- 而且 TCP 客户进程在发送完最后一个 TCP 确认报文段后,再经过 2MSL 时长就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的 TCP 连接中,不会出现旧连接中的报文段。

TCP保活计时器
如下图所示,TCP 双方已经建立了连接,后来,TCP 客户进程所在的主机突然出现了故障,则 TCP 服务器进程以后就不能再收到 TCP 客户进程发来的数据,因此,需要采取一定的措施,使 TCP 服务器进程不要再白白地等待下去,这就需要使用 TCP 保活计时器。
TCP 服务器进程每收到一次 TCP 客户进程的数据,就重新设置并启动保活计时器。若保活计时器定时周期内未收到 TCP 客户进程发来的数据,则当保活计时器到时后,TCP服务器进程就向 TCP 客户进程发送一个探测报文段,以后则每隔 75 秒发送一次,若一连发送 10 个探测报文段后仍无 TCP 客户进程的响应,TCP 服务器进程就认为 TCP 客户进程所在主机出了故障,接着就关闭这个连接。