传输层 II(TCP协议——协议的特点、报文段、连接管理)【★★★★】
(★★)代表非常重要的知识点,(★)代表重要的知识点。
一、TCP 协议的特点
TCP 是在不可靠的 IP 层之上实现的可靠的数据传输协议,它主要解决传输的可靠、有序、无丢失和不重复问题。TCP 是 TCP/IP 体系中非常复杂的一个协议。下面介绍 TCP 最主要的特点:
- TCP 是面向连接的运输层协议,TCP 连接是一条逻辑连接。这就是说,应用程序在使用 TCP 协议之前,必须先建立 TCP 连接。在传送数据完毕后,必须释放已经建立的 TCP 连接。
也就是说,应用进程之间的通信好像在“打电话”:通话前要先拨号建立连接,通话结束后要挂机释放连接。 - 每一条 TCP 连接只能有两个端点(endpoint),每一条 TCP 连接只能是点对点的(一对一)。
- TCP 提供可靠交付的服务。通过 TCP 连接传送的数据,无差错、不丢失、不重复,并且按序到达。
- TCP 提供全双工通信,允许通信双方的应用进程在任何时候都能发送数据。为此 TCP 连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据。
发送缓存用来暂时存放以下数据:
① 发送应用程序传送给发送方 TCP 准备发送的数据;
② TCP 已发送但尚未收到确认的数据。
接收缓存用来暂时存放以下数据:
① 按序到达但尚未被接收应用程序读取的数据;
② 不按序到达的数据。
- 面向字节流。TCP 中的“流”(stream)指的是流入到进程或从进程流出的字节序列。“面向字节流”的含义是:虽然应用程序和 TCP 的交互是一次一个数据块(大小不等),但 TCP 把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。TCP 并不知道所传送的字节流的含义。
TCP 不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小的关系(例如,发送方应用程序交给发送方的 TCP 共 10 个数据块,但接收方的 TCP 可能只用了 4 个数据块就把收到的字节流交付上层的应用程序)。但接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。当然,接收方的应用程序必须有能力识别收到的字节流,把它还原成有意义的应用层数据。上图是上述概念的示意图。
为了突出示意图的要点,我们只画出了一个方向的数据流。但请注意,在实际的网络中,一个 TCP 报文段包含上千个字节是很常见的,而图中的各部分都只画出了几个字节,这仅仅是为了更方便地说明“面向字节流”的概念。另一点很重要的是:上图中的 TCP 连接是一条虚连接(也就是逻辑连接),而不是一条真正的物理连接。TCP 报文段先要传送到 IP 层,加上 IP 首部后,再传送到数据链路层。再加上数据链路层的首部和尾部后,才离开主机发送到物理链路。
上图指出, TCP 和 UDP 在发送报文时所采用的方式完全不同。UDP 报文的长度由发送应用进程决定,而 TCP 并不关心应用进程一次把多长的报文发送到 TCP 的缓存中,而是根据接收方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少个字节。如果应用进程传送到 TCP 缓存的数据块太长, TCP 就可以把它划分短一些再传送。如果应用进程一次只发来一个字节(TCP 缓存的数据块太短), TCP 也可以等待积累有足够多的字节后再构成报文段发送出去。
【思考】:假定在一个互联网中,所有链路的传输都不出现差错,所有结点也都不会发生故障。试问在这种情况下, TCP 的”可靠交付”的功能是否就是多余的?
【答】:不是多余的。TCP 的“可靠交付”功能在互联网中起着至关重要的作用。至少在以下的情况下, TCP 的“可靠交付”功能是必不可少的。
I、每个 IP 数据报独立地选择路由,因此在到达目的主机时有可能出现失序。
II、由于路由选择的计算出现错误,导致 IP 数据报在互联网中转圈。最后数据报首部中的生存时间(TTL)的数值下降到零。这个数据报在中途就被丢失。
III、某个路由器突然出现很大的通信量,以致路由器来不及处理到达的数据报。因此有的数据报被丢弃。
以上列举的问题表明:必须依靠 TCP 的“可靠交付”功能才能保证在目的主机的目的进程中接收到正确的报文。
二、TCP 报文段
TCP 虽然是面向字节流的,但 TCP 传送的数据单元却是报文段。TCP 报文段既可以用来运载数据,又可以用来建立连接、释放连接和应答。一个 TCP 报文段分为首部和数据两部分,整个 TCP 报文段作为 IP 数据报的数据部分封装在 IP 数据报中。TCP 的全部功能都体现在它首部中各字段的作用。因此,只有弄清 TCP 首部各字段的作用才能掌握 TCP 的工作原理。下面讨论 TCP 报文段的首部格式。
TCP 报文段首部的前 20 个字节是固定的(如下图所示) ,后面有 4n 字节是根据需要而增加的选项(n 是整数)。因此 TCP 首部的长度为 4 字节的整数倍、最小长度是 20 字节。
首部固定部分各字段的意义如下:
-
源端口和目的端口:各占 2 个字节,分别写入源端口号和目的端口号。和 UDP 的分用相似,TCP 的分用功能也是通过端口实现的。
-
序号:占 4 字节。序号范围是 [0 , 232 - 1] ,共 232(即 4294967296)个序号。序号增加到 232 - 1 后,下一个序号就又回到 0 ,也就是说,序号使用 mod 232 运算。TCP 是面向字节流的,在一个 TCP 连接中传送的字节流中的每一个字节都按顺序编号。整个要传送的字节流的起始序号必须在连接建立时设置。首部中的序号字段值则指的是本报文段所发送的数据的第一个字节的序号。
例如,一报文段的序号字段值是 301,而携带的数据共有 100 字节。这就表明:本报文段的数据的第一个字节的序号是 301,最后一个字节的序号是 400 。显然,下一个报文段(如果还有的话)的数据序号应当从 401 开始,即下一个报文段的序号字段值应为 401 。这个字段的名称也叫做“报文段序号”。 -
确认号:占 4 字节,是期望收到对方下一个报文段的第一个数据字节的序号。若确认号= N,则表明:到序号 N - 1 为止的所有数据都已正确收到。
例如,B 正确收到了 A 发送过来的一个报文段,其序号字段值是 501,而数据长度是 200 字节(序号501 ~ 700),这表明 B 正确收到了 A 发送的到序号 700 为止的数据。因此, B 期望收到 A 的下一个数据序号是 701,于是 B 在发送给 A 的确认报文段中把确认号置为 701 。
由于序号字段有 32 位长,可对 4GB(即 4 千兆字节)的数据进行编号。在一般情况下可保证当序号重复使用时,旧序号的数据早已通过网络到达终点了。
-
数据偏移(即首部长度):占 4 位,这里不是 IP 数据报分片的那个数据偏移,而是指出 TCP 报文段的首部长度,由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的。它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。
但应注意,“数据偏移”的单位是 32 位字(即以 4 字节长的字为计算单位)。由于 4 位二进制数能够表示的最大十进制数字是 15,因此数据偏移的最大值是 60 字节,这也是 TCP 首部的最大长度(即选项长度不能超过 40 字节)。 -
保留:占 6 位,保留为今后使用,但目前应置为 0 。
-
紧急 URG(URGent):当 URG = 1 时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据),而不要按原来的排队顺序来传送。
例如,已经发送了很长的一个程序要在远地的主机上运行。但后来发现了一些问题,需要取消该程序的运行。因此用户从键盘发出中断命令(Control + C)。如果不使用紧急数据,那么这两个字符将存储在接收 TCP 的缓存末尾。只有在所有的数据被处理完毕后这两个字符才被交付接收方的应用进程。这样做就浪费了许多时间。当 URG 置 1 时,发送应用进程就告诉发送方的 TCP 有紧急数据要传送。于是发送方 TCP 就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍是普通数据。这时要与首部中紧急指针(Urgent Pointer)字段配合使用。 -
确认 ACK(ACKnowledgment):仅当 ACK = 1 时确认号字段才有效。当 ACK = 0 时,确认号无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1 。
-
推送 PSH(PuSH):当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下, TCP 就可以使用推送(push)操作。这时,发送方 TCP 把 PSH 置 1,并立即创建一个报文段发送出去。接收方 TCP 收到 PSH = 1 的报文段,就尽快地(即“推送”向前)交付接收应用进程,而不再等到整个缓存都填满了后再向上交付。
注:虽然应用程序可以选择推送操作,但推送操作很少使用。 -
复位 RST(ReSeT,也可称为重建位或重置位):当 RST = 1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。RST 置 1 还用来拒绝一个非法的报文段或拒绝打开一个连接。
-
同步 SYN(SYNchronization):在连接建立时用来同步序号。当 SYN = 1 而 ACK = 0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使 SYN = 1 和 ACK = 1。因此, SYN 置为 1 就表示这是一个连接请求或连接接受报文。
-
终止 FIN(FINis):用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。
-
窗口:占 2 字节。窗口值是 [0 , 216 - 1] 之间的整数。窗口指的是发送本报文段的一方的接收窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据。
例如,发送了一个报文段,其确认号是 701,窗口字段是 1000 。这就是告诉对方:“从 701 号算起,我(即发送此报文段的一方)的接收缓存空间还可接收 1000 个字节数据(字节序号是 701 ~ 1700) ,你在给我发送数据时,必须考虑到这一点。”
总之,应当记住:窗口字段明确指出了现在允许对方发送的数据量。窗口值经常在动态变化着。
-
检验和:占 2 字节。检验和字段检验的范围包括首部和数据这两部分。和 UDP 用户数据报一样,在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部。伪首部的格式与 UDP 用户数据报的伪首部一样。但应把伪首部第 4 个字段中的 17 改为 6(TCP 的协议号是 6),把第 5 字段中的 UDP 长度改为 TCP 长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。若使用 IPv6 ,则相应的伪首部也要改变。
-
紧急指针:占 2 字节。紧急指针仅在 URG = 1 时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。因此,紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时, TCP 就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为零时也可发送紧急数据。
-
选项:长度可变,最长可达 40 字节。当没有使用“选项”时, TCP 的首部长度是 20 字节。TCP最初只规定了一种选项,即最大报文段长度(MaximumSegment Size,MSS)。MSS 是 TCP 报文段中的数据字段的最大长度(注意仅仅是数据字段),数据字段加上 TCP 首部才等于整个的 TCP 报文段。所以 MSS 并不是整个 TCP 报文段的最大长度,而是“ TCP 报文段长度减去 TCP 首部长度”。
【思考】:为什么要规定一个最大报文段长度 MSS 呢?MSS 设置得太大或太小会有什么影响?
【答】:这并不是考虑接收方的接收缓存可能放不下 TCP 报文段中的数据。实际上, MSS 与接收窗口值没有关系。我们知道, TCP 报文段的数据部分,至少要加上 40 字节的首部(TCP 首部 20 字节和 IP 首部 20 字节,这里都还没有考虑首部中的选项部分),才能组装成一个 IP 数据报。若选择较小的 MSS 长度,网络的利用率就降低。设想在极端的情况下,当 TCP 报文段只含有 1 字节的数据时,在 IP 层传输的数据报的开销至少有 40 字节(包括 TCP 报文段的首部和 IP 数据报的首部)。这样,对网络的利用率就不会超过 1/41 。到了数据链路层还要加上一些开销。但反过来,若 TCP 报文段非常长,那么在 IP 层传输时就有可能要分解成多个短数据报片。在终点要把收到的各个短数据报片装配成原来的 TCP 报文段。当传输出错时还要进行重传。这些也都会使开销增大。
因此, MSS 应尽可能大些,只要在 IP 层传输时不需要再分片就行。由于 IP 数据报所经历的路径是动态变化的,因此在这条路径上确定的不需要分片的 MSS,如果改走另一条路径就可能需要进行分片。因此最佳的 MSS 是很难确定的。在连接建立的过程中,双方都把自己能够支持的 MSS 写入这一字段(一方把 MSS 值设定好以后通知另一方),以后就按照这个数值传送数据,两个传送方向可以有不同的 MSS 值。若主机未填写这一项,则 MSS 的默认值是 536 字节长。因此,所有在互联网上的主机都应能接受的报文段长度是 536 + 20(固定首部长度)= 556 字节。
- 填充:这是为了使整个首部长度是 4 字节的整数倍。
【拓展】:
随着互联网的发展,又陆续增加了几个选项。如窗口扩大选项、时间戳选项等,之后又增加了有关选择确认(SACK)选项。
-
窗口扩大选项是为了扩大窗口。我们知道, TCP 首部中窗口字段长度是 16 位,因此最大的窗口大小为 64K 字节。虽然这对早期的网络是足够用的,但对于包含卫星信道的网络(这种信道常称为长粗管道 long fat pipe),传播时延和带宽都很大,要获得高吞吐率需要更大的窗口大小。
窗口扩大选项占 3 字节,其中有一个字节表示移位值 S 。新的窗口值等于 TCP 首部中的窗口位数从 16 增大到(16 + S)。移位值允许使用的最大值是 14,相当于窗口最大值增大到 2(16+14) - 1 = 230 - 1 。
窗口扩大选项可以在双方初始建立 TCP 连接时进行协商。如果连接的某一端实现了窗口扩大,当它不再需要扩大其窗口时,可发送 S = 0 的选项,使窗口大小回到 16 。 -
时间戳选项占 10 字节,其中最主要的字段是时间戳值字段(4 字节)和时间戳回送回答字段(4 字节)。时间戳选项有以下两个功能:
① 用来计算往返时间 RTT 。发送方在发送报文段时把当前时钟的时间值放入时间戳字段,接收方在确认该报文段时把时间戳字段值复制到时间戳回送回答字段。因此,发送方在收到确认报文后,可以准确地计算出 RTT 来。
② 用于处理 TCP 序号超过 232 的情况,这又称为防止序号绕回 PAWS(Protect Against Wrapped Sequence numbers)。我们知道, TCP 报文段的序号只有 32 位,而每增加 232 个序号就会重复使用原来用过的序号。当使用高速网络时,在一次 TCP 连接的数据传送中序号很可能会被重复使用。例如,当使用 1.5 Mbit/s 的速率发送报文段时,序号重复要 6 小时以上。但若用 2.5 Gbit/s 的速率发送报文段,则不到 14 秒钟序号就会重复。为了使接收方能够把新的报文段和迟到很久的报文段区分开,可以在报文段中加上这种时间戳。
三、TCP 连接管理(★★)
TCP 是面向连接的协议,运输连接是用来传送 TCP 报文的。TCP 运输连接的建立和释放是每一次面向连接的通信中必不可少的过程。因此,运输连接就有三个阶段,即:连接建立、数据传送和连接释放。运输连接的管理就是使运输连接的建立和释放都能正常地进行。
在 TCP 连接建立的过程中,要解决以下三个问题:
- 要使每一方能够确知对方的存在。
- 要允许双方协商一些参数(如最大窗口值、是否使用窗口扩大选项、时间戳选项等)。
- 能够对运输实体资源(如缓存大小、连接表中的项目等)进行分配。
TCP 把连接作为最基本的抽象,每一条 TCP 连接有两个端点。那么, TCP 连接的端点是什么呢?不是主机,不是主机的 IP 地址,不是应用进程,也不是运输层的协议端口。TCP 连接的端点叫做套接字(socket)或插口。
根据RFC 793 的定义:端口号拼接到(concatenated with)IP 地址即构成了套接字。因此,套接字的表示方法是在点分十进制的 IP 地址后面写上端口号,中间用冒号或逗号隔开。例如,若 IP 地址是 192.3.4.5 而端口号是 80 ,那么得到的套接字就是 (192.3.4.5 : 80) 。
总之,我们有:
套接字 socket = (IP 地址 : 端口号)
每一条 TCP 连接唯一地被通信两端的两个端点(即两个套接字)所确定。即:
TCP 连接 ::= {socket1 , socket2} = {(IP1 : port1) , (IP2 : port2)}
这里 IP1 和 IP2 分别是两个端点主机的 IP 地址,而 port1 和 port2 分别是两个端点主机中的端口号。TCP 连接的两个套接字就是 socket1 和 socket2 。
总之, TCP 连接就是由协议软件所提供的一种抽象。虽然有时为了方便,我们也可以说,在一个应用进程和另一个应用进程之间建立了一条 TCP 连接,但一定要记住: TCP 连接的端点是个很抽象的套接字,即 (IP 地址 :端口号)。也还应记住:同一个 IP 地址可以有多个不同的 TCP 连接,而同一个端口号也可以出现在多个不同的 TCP 连接中。
TCP 连接的建立采用客户/服务器方式。主动发起连接建立的应用进程叫做客户(Client),而被动等待连接建立的应用进程叫做服务器(Server)。
1. TCP 连接的建立(三次握手)
TCP 建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个 TCP 报文段。下图画出了三报文握手建立 TCP 连接的过程。TCP 连接建立的过程通常称为“三次握手”。
假定主机 A 运行的是 TCP 客户程序,而 B 运行 TCP 服务器程序。最初两端的 TCP 进程都处于 CLOSED(关闭)状态。
图中在主机下面的方框分别是 TCP 进程所处的状态。请注意,在本例中, A 主动打开连接,而 B 被动打开连接。
传输控制块 TCB(Transmission Control Block)存储了每一个连接中的一些重要信息,如:TCP 连接表,指向发送和接收缓存的指针,指向重传队列的指针,当前的发送和接收序号等。
一开始(连接建立前), B 的 TCP 服务器进程先创建传输控制块 TCB ,准备接受客户进程的连接请求。然后服务器进程就处于 LISTEN(收听)状态,等待客户的连接请求。如有,即作出响应。
-
第一步:A 的 TCP 客户进程也是首先创建传输控制模块 TCB 。然后,在打算建立 TCP 连接时,向 B 发出连接请求报文段,这时首部中的同步位 SYN = 1 ,同时选择一个初始序号 seq = x 。TCP 规定,SYN 报文段(即 SYN = 1 的报文段)不能携带数据,但要消耗掉一个序号。这时,TCP 客户进程进入 SYN-SENT(同步已发送)状态。
-
第二步:B 收到连接请求报文段后,如同意建立连接,则向 A 发送确认,并为该 TCP 连接分配缓存和变量。在确认报文段中应把 SYN 位和 ACK 位都置 1 ,确认号是 ack = x + 1 ,同时也为自己选择一个初始序号 seq = y 。请注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。这时 TCP 服务器进程进入 SYN-RCVD(同步收到)状态。
-
第三步:TCP 客户进程收到 B 的确认后,还要向 B 给出确认,并为该 TCP 连接分配缓存和变量。确认报文段的 ACK 置 1,确认号 ack = y + 1 ,而自己的序号 seq = x + 1 。TCP 的标准规定, ACK 报文段可以携带数据。但如果不携带数据则不消耗序号,在这种情况下,下一个数据报文段的序号仍是 seq = x + 1 。这时, TCP 连接已经建立, A 进入 ESTABLISHED(已建立连接)状态。
当 B 收到 A 的确认后,也进入 ESTABLISHED 状态。
成功进行以上三步后,就建立了 TCP 连接,接下来就可以传送应用层数据。TCP 提供的是全双工通信,因此通信双方的应用进程在任何时候都能发送数据。
【思考】:为什么不采用“两次握手”建立连接呢?为什么 A 最后还要发送一次确认呢?
【答】:这主要是为了防止已失效的连接请求报文段突然又传送到了 B,因而产生错误。所谓“已失效的连接请求报文段”是这样产生的。考虑一种正常情况, A 发出连接请求,但因连接请求报文丢失而未收到确认。于是 A 再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。A 共发送了两个连接请求报文段,其中第一个丢失,第二个到达了 B,没有“已失效的连接请求报文段“。
现假定出现一种异常情况,即 A 发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达 B 。本来这是一个早已失效的报文段。但 B 收到此失效的连接请求报文段后,就误认为是 A 又发出一次新的连接请求。于是就向 A 发出确认报文段,同意建立连接。假定不采用报文握手,那么只要 B 发出确认,新的连接就建立了。
由于现在 A 并没有发出建立连接的请求,因此不会理睬 B 的确认,也不会向 B 发送数据。但 B 却以为新的运输连接已经建立了,并一直等待 A 发来数据。B 的许多资源就这样白白浪费了。
采用三报文握手的办法,可以防止上述现象的发生。例如在刚才的异常情况下, A 不会向 B 的确认发出确认。B 由于收不到确认,就知道 A 并没有要求建立连接。
2. TCP 连接的释放(四次挥手)
TCP 连接释放过程比较复杂,我们仍结合双方状态的改变来阐明连接释放的过程。参与 TCP 连接的两个进程中的任何一个都能终止该连接。下图画出了四报文握手释放 TCP 连接的过程。TCP 连接释放的过程通常称为“四次挥手”。
- 第一步:数据传输结束后,通信的双方都可释放连接。现在 A 和 B 都处于 ESTABLISHED 状态(如上图所示)。A 的应用进程先向其 TCP 发出连接释放报文段,并停止再发送数据,主动关闭 TCP 连接。A 把连接释放报文段首部的终止控制位 FIN 置 1 ,其序号 seq = u ,它等于前面已传送过的数据的最后一个字节的序号加 1 。这时 A 进入 FIN-WAIT-1(终止等待 1)状态,等待 B 的确认。请注意, TCP 规定, FIN 报文段即使不携带数据,它也消耗掉一个序号。
TCP 是全双工的,即可以想象为一条 TCP 连接上有两条数据通路,发送 FIN 的一端不能再发送数据,即关闭了其中一条数据通路,但对方还可以发送数据。
-
第二步:B 收到连接释放报文段后即发出确认,确认号是 ack = u + 1 ,而这个报文段自己的序号 seq = v(等于 B 前面已传送过的数据的最后一个字节的序号加 1)。然后 B 就进入 CLOSEWAIT(关闭等待)状态。TCP 服务器进程这时应通知高层应用进程,因而从 A 到 B 这个方向的连接就释放了,这时的 TCP 连接处于半关闭(half-close)状态,即 A 已经没有数据要发送了,但 B 若发送数据, A 仍要接收。也就是说,从 B 到 A 这个方向的连接并未关闭,这个状态可能会持续一段时间。A 收到来自 B 的确认后,就进入 FIN-WAIT-2(终止等待 2)状态,等待 B 发出的连接释放报文段。
-
第三步:若 B 已经没有要向 A 发送的数据,其应用进程就通知 TCP 释放连接。这时 B 发出的连接释放报文段必须使 FIN = 1 。现假定 B 的序号为 w(在半关闭状态 B 可能又发送了一些数据)。B 还必须重复上次已发送过的确认号 ack = u + 1 。这时 B 就进入 LAST-ACK(最后确认)状态,等待 A 的确认。
-
第四步:A 在收到 B 的连接释放报文段后,必须对此发出确认。在确认报文段中把 ACK 置 1,确认号 ack = w + 1 ,而自己的序号是 seq = u + 1(根据 TCP 标准,前面发送过的 FIN 报文段要消耗一个序号)。然后进入到 TIME-WAIT(时间等待)状态。请注意,现在 TCP 连接还没有释放掉。必须经过时间等待计时器(TIME-WAIT timer)设置的时间 2MSL 后, A 才进入到 CLOSED 状态。若服务器收到连接释放请求后不再发送数据,则从客户机发出 FIN 报文段时刻算起,客户机释放连接的最短时间为 1RTT + 2MSL ,服务器释放连接的最短时间为 1.5RTT 。
时间 MSL 叫做最长报文段寿命(Maximum Segment Lifetime),RFC 793 建议设为 2 分钟。因此,从 A 进入到 TIME-WAIT 状态后,要经过 4 分钟才能进入到 CLOSED 状态,才能开始建立下一个新的连接。当 A 撤销相应的传输控制块 TCB 后,就结束了这次的 TCP 连接。
除时间等待计时器外, TCP 还设有一个保活计时器(keepalive timer)。设想有这样的情况:客户已主动与服务器建立了 TCP 连接。但后来客户端的主机突然出故障。显然,服务器以后就不能再收到客户发来的数据。因此,应当有措施使服务器不要再白白等待下去。这就是使用保活计时器。服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两小时。若两小时没有收到客户的数据,服务器就发送一个探测报文段,以后则每隔 75 秒钟发送一次。若一连发送 10 个探测报文段后仍无客户的响应,服务器就认为客户端出了故障,接着就关闭这个连接。
【思考】:为何不采用“三次握手”释放连接?且为什么 A 在 TIME-WAIT 状态必须等待 2MSL 的时间呢?
【答】:这有两个理由。第一,为了保证 A 发送的最后一个 ACK 报文段能够到达 B 。这个 ACK 报文段有可能丢失,因而使处在 LAST-ACK 状态的 B 收不到对已发送的 FIN + ACK 报文段的确认。B 会超时重传这个 FIN + ACK 报文段,而 A 就能在 2MSL 时间内收到这个重传的 FIN + ACK 报文段。接着 A 重传一次确认,重新启动 2MSL 计时器。最后, A 和 B 都正常进入到 CLOSED 状态。如果 A 在 TIME-WAIT 状态不等待一段时间,而是在发送完 ACK 报文段后立即释放连接,那么就无法收到 B 重传的 FIN + ACK 报文段,因而也不会再发送一次确认报文段。这样,B 就无法按照正常步骤进入 CLOSED 状态。
第二,防止“已失效的连接请求报文段”出现在本连接中。A 在发送完最后一个 ACK 报文段后,再经过时间 2MSL ,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。
B 只要收到了 A 发出的确认,就进入 CLOSED 状态。同样, B 在撤销相应的传输控制块 TCB 后,就结束了这次的 TCP 连接。我们注意到, B 结束 TCP 连接的时间要比 A 早一些。
3. 总结
对上述 TCP 连接建立和释放的总结如下:
-
建立连接。分为 3 步:
① SYN = 1,seq = x 。
② SYN = 1,ACK = 1,seq = y,ack = x + 1 。
③ ACK = 1,seq = x + 1,ack = y + 1 。 -
释放连接。分为 4 步:
① FIN = 1,seq = u 。
② ACK = 1,seq = v,ack = u + 1 。
③ FIN = 1,ACK = 1,seq = w,ack = u + 1 。
④ ACK = 1,seq = u + 1,ack = w + 1 。
【思考】:为什么 TCP 在建立连接时不能每次都选择相同的、固定的初始序号?
- 假定主机 A 和 B 频繁地建立连接,传送一些 TCP 报文段后,再释放连接,然后又不断地建立新的连接、传送报文段和释放连接。
- 假定每次建立连接时,主机 A 都选择相同的、固定的初始序号,如选择 1 。
- 假定主机 A 发出的某些 TCP 报文段在网络中会滞留较长时间,以致主机 A 超时重传这些 TCP 报文段。
- 假定有一些在网络中滞留时间较长的 TCP 报文段最后终于到达主机 B ,但这时传送该报文段的那个连接早已释放,而在到达主机 B 时的 TCP 连接是一条新的 TCP 连接。
这样,工作在新的 TCP 连接的主机 B 就有可能会接收在旧的连接传送的、已无意义的、过时的 TCP 报文段(因为这个 TCP 报文段的序号有可能正好处在当前新连接所用的序号范围之中),结果产生错误。因此,必须使得迟到的 TCP 报文段的序号不处在新连接所用的序号范围之中。这样, TCP 在建立新的连接时所选择的初始序号一定要和前面的一些连接所用过的序号不同。因此,不同的 TCP 连接不能使用相同的初始序号。
4. TCP 的有限状态机
为了更清晰地看出 TCP 连接的各种状态之间的关系,下图给出了 TCP 的有限状态机。图中每一个方框即 TCP 可能具有的状态。每个方框中的大写英文字符串是 TCP 标准所使用的 TCP 连接状态名。状态之间的箭头表示可能发生的状态变迁。箭头旁边的字,表明引起这种变迁的原因,或表明发生状态变迁后又出现什么动作。请注意图中有三种不同的箭头。粗实线箭头表示对客户进程的正常变迁。粗虚线箭头表示对服务器进程的正常变迁。另一种细线箭头表示异常变迁。
我们可以把上图和前面的 TCP 连接的建立图与TCP 连接的释放图对照起来看。在建立图和释放图中左边客户进程从上到下的状态变迁,就是上图中粗实线箭头所指的状态变迁。而在建立图和释放图右边服务器进程从上到下的状态变迁,就是上图中粗虚线箭头所指的状态变迁。
四、例题
① 下列关于传输层协议中面向连接的描述,( C )是错误的。
A. 面向连接的服务需要经历 3 个阶段:连接建立、数据传输及连接释放
B. 当链路不发生错误时,面向连接的服务可以保证数据到达的顺序是正确的
C. 面向连接的服务有很高的效率和时间性能
D. 面向连接的服务提供了一个可靠的数据流
【因为面向连接的服务需要建立连接,且需要保证数据的有序性和正确性,所以它比无连接的服务开销大,而速度和效率方面也要比无连接的服务差一些。】
② TCP 协议规定 HTTP( C )进程的端口号为 80 。
A. 客户机
B. 解析
C. 服务器
D. 主机
【TCP 中端口号 80 标识 Web 服务器端的 HTTP 进程,客户端访问 Web 服务器的 HTTP 进程的端口号由客户端的操作系统动态分配。】
③ 下列关于 TCP 的端口的叙述中,错误的是( D )。
A. 客户端使用的端口号是动态规定的
B. 端口号长度为16 位
C. 端口号用于在通信中识别进程
D. 局域网内的计算机不能使用相同端口号
④ 下列( D )不是 TCP 服务的特点。
A. 字节流
B. 全双工
C. 可靠
D. 支持广播
【TCP 提供的是一对一全双工可靠的字节流服务,所以 TCP 并不支持广播。】
⑤ ( B )字段包含在 TCP 首部中,而不包含在 UDP 首部中。
A. 目的端口号
B. 序列号(序号)
C. 检验和
D. 目的 IP 地址
⑥ 当 TCP 报文段的标志字段中( B )为 1 ,表示出现严重错误,必须释放连接
A. URG
B. RST
C. ACK
D. FIN
⑦ TCP 报文段首部中窗口字段的值( C )。
A. 指明自己的拥塞窗口的尺寸
B. 指明对方的发送窗口的尺寸
C. 指明自己的接收窗口的尺寸
D. 指明对方的拥塞窗口的尺寸
【TCP 报文段首部中窗口字段的值指的是自己接收窗口的尺寸。】
⑧ 在采用 TCP 连接的数据传输阶段,若发送端的发送窗口值由 1000 变为 2000 ,则发送端在收到一个确认之前可以发送( B )。
A. 2000 个 TCP 报文段
B. 2000B
C. 1000B
D. 1000 个 TCP 报文段
【发送窗口值表明发送端在未收到确认之前可以发送的最大字节数。】
⑨ A 和 B 建立了 TCP 连接,当 A 收到确认号为 100 的确认报文段时,表示( C )。
A. 报文段 99 已收到
B. 报文段 100 已收到
C. 末字节序号为 99 的报文段已收到
D. 末字节序号为 100 的报文段已收到
【TCP 的确认号是指明接收方下一次希望收到的报文段的数据部分第一个字节的编号。】
⑩ 为保证数据传输的可靠性,TCP 采用了对( A )确认的机制。
A. 报文段
B. 分组
C. 字节
D. 比特
虽然 TCP 是面向字节的,对每个字节都进行编号,但并不是对接收到的每个字节都要发回确认,而是在收到整个报文段后才发回一个确认,确认的序号也是报文段的边界字节序号,并不能只对报文段中的某些字节序号进行确认,所以 TCP 采用的是对报文段的确认机制。
(11) 以下关于 TCP 工作原理与过程的描述中,错误的是( C )。
A. TCP连接建立过程需要经过“三次握手”的过程
B. TCP传输连接建立后,客户端与服务器端的应用进程进行全双工的字节流传输
C. TCP 传输连接的释放过程很复杂,只有客户端可以主动提出释放连接的请求
D. TCP 连接的释放需要经过“四次挥手”的过程
(12) TCP 使用三次握手协议来建立连接,设 A、B 双方发送报文的初始序列号分别为 X 和 Y,A 发送( ① A )的报文给 B ,B 接收到报文后发送( ② C )的报文给 A ,然后 A 发送一个确认报文给 B 便建立了连接(注意,ACK 的下标为捎带的序号)。
① A. SYN = 1,序号 = X
B. SYN = 1,序号 = X + 1,ACKX = 1
C. SYN = 1,序号 = Y
D. SYN = 1,序号 = Y,ACKY+1 = 1
② A. SYN = 1,序号 = X + 1
B. SYN = 1,序号 = X + 1,ACKX = 1
C. SYN = 1,序号 = Y,ACKX = 1
D. SYN = 1,序号 = Y,ACKY+1 = 1
(13) TCP “三次握手”过程中,第二次“握手”时,发送的报文段中( D )标志位被置为 1 。
A. SYN
B. ACK
C. ACK 和 RST
D. SYN 和 ACK
【在 TCP 的“三次握手”中,第二次握手时,SYN 和 ACK 均被置为 1 。】
(14) TCP 采用三报文握手建立连接,其中第三个报文是( C )。
A. TCP 连接请求
B. 对 TCP 连接请求的确认
C. 对 TCP 连接请求确认的确认
D. TCP 普通数据
【TCP 采用三报文握手建立连接,其中第一个报文是 TCP 连接请求,第二个报文是对 TCP 连接请求的确认,第三个报文是对 TCP 连接请求确认的确认。】
(15) A 和 B 之间建立了 TCP 连接,A 向 B 发送了一个报文段,其中序号字段 seq = 200,确认号字段 ack = 201,数据部分有 2 个字节,则在 B 对该报文的确认报文段中( C )。
A. seq=202,ack=200
B. seq=201,ack=201
C. seq=201,ack=202
D. seq=202,ack=201
(16) TCP 的通信双方,有一方发送了带有 FIN 标志的数据段后,表示( B )。
A. 将断开通信双方的 TCP 连接
B. 单方面释放连接,表示本方已经无数据发送,但可以接收对方的数据
C. 中止数据发送,双方都不能发送数据
D. 连接被重新建立
【FIN 位用来释放一个连接,它表示本方已没有数据要传输。然而,在关闭一个连接后,对方还可以继续发送数据,所以还有可能接收到数据。】
(17) 假设 TCP 客户与 TCP 服务器的通信已结束,端到端的往返时间为 RTT 。t 时刻 TCP 客户请求断开连接,则从 t 时刻起 TCP 服务器释放该连接的最短时间是( C )。
A. 0.5 RTT
B. 1 RTT
C. 1.5 RTT
D. 2 RTT
(18) 甲发起与乙的 TCP 连接,甲选择的初始序号为 200,若甲和乙建立连接过程中最后一个报文段不携带数据,则 TCP 连接建立后,甲给乙发送的数据报文段的序号为( C )。
A. 203
B. 202
C. 201
D. 200
【甲选择的初始序号为 200 ,建立 TCP 连接的第一个报文段不能携带数据,但要消耗一个序号。甲给乙发送的第二个报文段(第三次握手)的序号是 201 ,该报文段可以携带数据,若不携带数据,则不消耗序号,题中该报文段不携带数据,因此下一个数据报文段的序号仍是 201 。】
(19) A 发起与 B 的 TCP 连接,A 选择的初始序号为 1666,连接建立过程中未发送任何数据,TCP 连接建立后,A 给 B 发送了 1000B 数据,B 正确接收后发送给 A 的确认序号是( C )。
A. 1667
B. 2666
C. 2667
D. 2668
(20) 主机 1 的进程以端口 x 和主机 2 的端口 y 建立了一条 TCP 连接,这时若希望再在这两个端口间建立一个 TCP 连接,则会( A )。
A. 建立失败,不影响先建立连接的传输
B. 建立成功,且两个连接都可以正常传输
C. 建立成功,先建立的连接被断开
D. 建立失败,两个连接都被断开
【一条连接使用它们的套接字来表示,因此 (1, x) - (2, y) 是在两个端口之间唯一可能的连接。而后建立的连接会被阻止。】
(21) 【2009 统考真题】主机甲与主机乙之间已建立一个 TCP 连接,主机甲向主机乙发送了两个连续的 TCP 段,分别包含 300B 和 500B 的有效载荷,第一个段的序列号为 200 ,主机乙正确接收到这两个数据段后,发送给主机甲的确认序列号是( D )。
A. 500
B. 700
C. 800
D. 1000
(22) 【2011统考真题】主机甲向主机乙发送一个(SYN = 1,seq = 11220)的 TCP 段,期望与主机乙建立 TCP 连接,若主机乙接受该连接请求,则主机乙向主机甲发送的正确的 TCP 段可能是( C )。
A.(SYN = 0,ACK = 0,seg = 11221,ack = 11221)
B.(SYN = 1,ACK = 1,seq = 11220,ack = 11220)
C.(SYN = 1,ACK = 1,seq = 11221,ack = 11221)
D.(SYN = 0,ACK = 0,seg = 11220,ack = 11220)
(23) 【2011统考真题】主机甲与主机乙之间已建立一个 TCP 连接,主机甲向主机乙发送了 3 个连续的 TCP 段,分别包含 300B、400B 和 500B 的有效载荷,第 3 个段的序号为 900 。若主机乙仅正确接收到第 1 个段和第 3 个段,则主机乙发送给主机甲的确认序号是( B )。
A. 300
B. 500
C. 1200
D. 1400
(24) 【2013 统考真题】主机甲与主机乙之间已建立一个 TCP 连接,双方持续有数据传输,且数据无差错与丢失。若甲收到一个来自己的 TCP 段,该段的序号为 1913、确认序号为 2046 、有效载荷为 100B ,则甲立即发送给乙的 TCP 段的序号和确认序号分别是( B )。
A. 2046 、2012
B. 2046 、2013
C. 2047 、2012
D. 2047 、2013
(25) 【2019 统考真题】若主机甲主动发起一个与主机乙的 TCP 连接,甲、乙选择的初始序列号分别为 2018 和 2046 ,则第三次握手 TCP 段的确认序列号是( D )。
A. 2018
B. 2019
C. 2046
D. 2047
(26) 【2020 统考真题】若主机甲与主机乙建立 TCP 连接时,发送的 SYN 段中的序号为 1000 ,在断开连接时,主机甲发送给主机乙的 FIN 段中的序号为 5001 ,则在无任何重传的情况下,甲向乙已经发送的应用层数据的字节数为( C )。
A. 4002
B. 4001
C. 4000
D. 3999
【甲与乙建立 TCP 连接时发送的 SYN 段中的序号为 1000(TCP 规定,SYN 段不能携带数据,但要消耗一个序号),则在数据传输阶段所用起始序号为 1001 。断开连接时,甲发送给乙的 FIN 段中的序号为 5001(TCP 规定,FIN 段即使不携带数据,也要消耗一个序号),因此已发送数据的最后一个字节的序号为 5000 ,即甲向乙已发送数据的字节序号为 1001 ~ 5000 ,共 4000 字节。】
(27) 【2021统考真题】若客户首先向服务器发送 FIN 段请求断开 TCP 连接,则当客户收到服务器发送的 FIN 段并向服务器发送 ACK 段后,客户的 TCP 状态转换为( B )。
A. CLOSE WAIT
B. TIMEWAIT
C. FIN WAIT 1
D. FIN WAIT 2
(28) 【2021 统考真题】若大小为 12B 的应用层数据分别通过 1 个 UDP 数据报和 1 个 TCP 段传输,则该 UDP 数据报和 TCP 段实现的有效载荷(应用层数据)最大传输效率分别是( D )。
A. 37.5%, 16.7%
B. 37.5%, 37.5%
C. 60.0%, 16.7%
D. 60.0%, 37.5%
【UDP 首部有 8B ,TCP 首部最短有 20B 。】
(29) 【2022 统考真题】假设客户 C 和服务器 S 已建立一个 TCP 连接,通信往返时间 RTT = 50ms ,最长报文段寿命 MSL = 800ms ,数据传输结束后,C 主动请求断开连接。若从 C 主动向 S 发出 FIN 段时刻算起,则 C 和 S 进入 CLOSED 状态所需的时间至少分别是( D )。
A. 850 ms, 50 ms
B. 1650 ms, 50 ms
C. 850 ms, 75 ms
D. 1650 ms, 75 ms
【客户 C 收到服务器 S 发来的 FIN 报文段后,进入 CLOSED 状态还需等到 TIME-WAIT 结束,总用时至少为 1RTT + 2MSL = 50 + 800 × 2 = 1650ms 。服务器 S 进入 CLOSED 状态需要经过 3 次报文段的传输时间,即 1.5RTT = 75ms 。】