TCP学习总结(二):连接的建立与终止

1、建立连接之三次握手

TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须现在双方之间建立一条连接建立一条TCP连接分为三步:

  1. 请求端(通常称为客户端)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN,在这个例子中为1415531521)。这个SYN段为报文段1。
  2. 服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
  3. 客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。

这三个报文段完成连接的建立。这个过程也称为三次握手(three-way handshake)。
1.jpg
                 图1:建立连接与终止连接

发送第一个SYN的一端将执行主动打开(active open)。接受这个SYN并发回下一个SYN的另一端执行被动打开(passive open)(后续章节将介绍双方如何都执行主动打开)。

当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都具有不同的ISN。RFC793指出ISN可以看作是一个32比特的计数器,每4ms加1。这样选择序号的目的在于放置在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它作错误的解释。

    如何进行序号选择?在4.4BSD中,系统初始化时初始的发送序号被初始化为1。这种方法违背了Host Requirement RFC(在这个代码中的一个注释确认这是一个错误)。这个变量每0.5秒增加64000,并每隔9.5小时又回到0(对应这个计数器每8ms加1,而不是每4ms加1)。另外,每次建立一个连接后,这个变量将增加64000。

2、断开连接之四次挥手

建立一个连接需要三次握手,而断开一个连接时要经过4次挥手。这由TCP的半连接(half-close)造成的。既然一个TCP连接是全双工的(即数据在两个方向上能同时传递),因此每个方向必须单独的进行关闭。原则就是当一方完成它的数据发送任务之后就能发送一个FIN来终止这个方向上的连接。当一端收到一个FIN时,它必须通知应用层另一端已经终止了那个方向上的数据传送。发送FIN通常是应用层进行关闭的结果。

收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的TCP应用程序这样做,正常关闭过程如图1所示。

首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这个FIN)将执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭,后面我们将看到双方如何都执行主动关闭。

图1中的报文段4发起终止连接,它由客户端关闭连接时发出。当服务器收到这个FIN时,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。同时,TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符。接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN(报文段6),客户必须发回一个确认,并将确认序号设置为收到序号加1(报文段7)。
2.jpg
           图2:连接终止期间报文段的正常交换

图2显示了终止一个连接的典型握手顺序。我们省略了序号。在这个图中发送FIN将导致应用程序关闭它们的连接,这些FIN的ACK都是由TCP软件自动产生的。

3、最大报文段长度

最大报文段长度(MSS)表示TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的MSS。我们已经见过MSS都是1024,这导致IP数据报通常是40字节长:20字节的TCP首部和20字节的IP首部。当建立一个连接时,每一方都有用于通告它期望接收的MSS选项(MSS选项只能出现在SYN报文段中)。如果一方不接收来自另一方的MSS值,则MSS就定为默认值536字节(这个默认值允许20字节的IP首部和20字节的TCP首部以适合576字节的IP数据报)。

一般来说,如果没有分段发生,MSS还是越大越好(这也并不总是正确),报文段越大允许每个报文段传送的数据就越多,相对IP和TCP首部有更高的网络利用率。当TCP发送一个SYN时,或者是因为一个本地应用进程想发起一个连接,或者是因为另一端的主机收到了一个连接请求,它能将MSS值设置为外出接口上的MTU长度减去固定的IP首部和TCP首部长度。对一个以太网,MSS值可达1460字节。

4、TCP的半关闭

TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力,这就是所谓的半关闭。正如我们上一篇提到的只有很少的应用程序使用它。

为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据传送,因此发送一个文件结束(FIN)给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束(FIN)”。
3.jpg
                 图3:TCP半关闭的例子
图3显示了一个半关闭的典型例子。让左方的客户端开始半关闭,当然也可以由另一端开始。开始的两个报文段和图2是相同的:初始端发出的FIN,接着是另一端对这个FIN的ACK报文段。但后面就和图2不同,因为接收半关闭的一方仍能发送数据。我们只显示一个数据报文段和一个ACK报文段,但实际可能发送了许多数据报文段。当收到半关闭的一端在完成它的数据传送后,将发送一个FIN关闭这个方向的连接,这将传送一个文件结束符给发起这个半关闭的应用进程。当对第二个FIN进行确认后,这个连接并彻底关闭了。

5、TCP的状态变迁图

我们已经介绍了有关发起和终止TCP连接的规则,这些规则都能从图4所示的状态变迁图中得出。
4.jpg
                 图4:TCP的状态变迁图
在这个图中要注意的第一点是一个状态变迁的子集是“典型的”。我们用粗的实线箭头表示正常的客户端状态变迁,用粗的虚线箭头表示正常的服务器状态变迁。

第二点是两个导致进入ESTABLISHED状态的变迁对应打开一个连接,而两个导致从ESTABLISHED状态离开的变迁对应关闭一个连接。ESTABLISHED状态是连接双方能够进行双向数据传输的状态。

在图4中左下角4个状态放在一个虚线框内,并标为“主动关闭”,其他两个状态(CLOSE_WAIT和LAST_ACK)也用虚线框住,并 标为“被动关闭”。只有当SYN_RCVD状态是从LISTEN状态(正常情况)进入,而不是从SYN_SEND(同时打开)进入时,从SYN_RCVD回到LISTEN的状态变迁才是有效的。这意味着如果我们执行被动关闭(进入LISTEN),收到一个SYN,发送一个带ACK的SYN(进入SYN_RCVD),然后收到一个RST,而不是一个ACK,便又回到LISTEN状态并等待另一个连接请求的到来。

5.jpg
           图5:TCP正常连接建立和终止所对应的状态

图5显示了在正常的TCP连接的建立和终止过程中,客户与服务器所经历的不同状态

6、2MSL等待状态

TIME_WAIT状态也称为2MSL等待状态。每个具体的TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。我们知道这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。

RFC793指出MSL为2分钟。然而,实现中通常值是30秒,1分钟或者2分钟。对IP数据报TTL的限制是基于跳数,而不是定时器。对一个具体实现所给定的MSL值,处理原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL,这样可以让TCP再次发送最后的ACK以防止这个ACK丢失(另一端超时并重发最后的FIN)。这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接的插口只能在2MSL结束后才能再被使用。大多数TCP实现强加了更为严格的限制,在2MSL等待期间,插口中使用的本地端口在默认情况下不能再被使用。

在连接处于2MSL等待时,任何迟到的报文将被丢弃。因为处于2MSL等待的、由该插口对(socket pair)定义的连接在这段时间内不能被再用,因此当要建立一个有效的连接时,来自该连接的一个较早替身( incarnation)的迟到报文作为新连接的一部分不可能不被曲解(一个连接由一个插口对来定义。一个连接的新的实例(instance)称为该连接的替身)。

我们说图4中客户执行主动关闭并进入TIME_WAIT是正常的。服务器通常执行被动关闭,不会进入TIME_WAIT状态。这暗示如果我们终止一个客户程序,并立即重新启动这个客户程序,则这个新客户程序将不能重用相同的本地端口。这不会带来什么问题,因为客户使用本地端口,而并不关心这个端口号是什么。

然而,对于服务器,情况就有所不同,因为服务器使用熟知端口。如果我们终止一个已经建立连接的服务器程序,并试图立即重新启动这个服务器程序,服务器程序将不能把它的这个熟知端口赋值给它的端点,因为那个端口是处于2MSL连接的一部分。在重新启动服务器程序前,它需要在1 ~ 4分钟。

对于来自某个连接的较早替身的迟到报文段,2MSL等待可防止将它解释成使用相同插口对的新连接的一部分。但这只有在处于2MSL等待连接中的主机处于正常工作状态时才有效。如果使用处于2MSL等待端口的主机出现故障,它会在MSL秒内重新启动,并立即使用故障前仍处于2MSL的插口对来建立一个新的连接,如果是这样,在故障前从这个连接发出而迟到的报文段会被错误地当作属于重启后新连接的报文段。无论如何选择重启后新连接的初始序号,都会发生这种情况。

为了防止这种情况, RFC 793指出TCP在重启动后的MSL秒内不能建立任何连接。这就称为平静时间 (quiet time)。 只有极少的实现版遵守这一原则,因为大多数主机重启动的时间都比MSL秒要长。

7、同时打开和同时关闭

两个应用程序同时彼此执行主动打开的情况是可能的,尽管发生的可能性极小。每一方必须发送一个SYN,且这些SYN必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。这又称为同时打开( simultaneous open)。

TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接。当出现同时打开的情况时,状态变迁与图5所示的不同。两端几乎在同时发送SYN,并进入SYN_SEND状态。当每一端收到SYN时,状态变为SYN_RCVD(如图4),同时它们都再发SYN并对收到的SYN进行确认。当双方都收到SYN及相应的ACK时,状态都变迁为ESTABLISHED。图6显示了这些状态变迁过程。

6.jpg
           图6:同时打开期间报文段的交换

一个同时打开的连接需要交换4个报文段,比正常的3次握手多一个。此外,要注意的是我们没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器。

我们在以前讨论过一方(通常但不总是客户方)发送第一个FIN执行主动关闭。双方都执行主动关闭也是可能的, TCP协议也允许这样的同时关闭( simultaneous close)。

在图4中,当应用层发出关闭命令时,两端均从ESTABLISHED变为FIN_WAIT_1。这将导致双方各发送一个FIN,两个FIN经过网络传送后分别到达另一端。收到FIN后,状态由FIN_WAIT_1变迁到CLOSING,并发送最后的ACK。当收到最后的ACK时,状态变化为TIME_WAIT。图7总结了这些状态的变化。

7.jpg
           图7:同时关闭期间报文段的交换

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章