TCP连接的建立和终止

1. 引言

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

2. TCP连接建立

2.1 连接建立

为了了解一个TCP连接在建立及终止时发生了什么,键入下列命令:
telnet 10.31.10.19 7073
然后键入Ctrl+],使Telnet客户进程终止连接。

telnet命令与10.31.10.19:7073建立TCP连接。
tcpdump的输出,如图:

(1)请求端(客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号ISN。
(2)服务器发回包含服务器的初始序号的SYN报文段作为应答。同时,将确认序号设置为客户端的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
(3)客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认。
这三个报文段完成连接的建立,这个过程也称为三次握手(three-way handshake)

发送第一个SYN的一端将执行主动打开(active open)。接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open)。
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。

2.2 MSS

最大报文段长度(MSS)标示TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的MSS(MSS选项只能出现在SYN报文段中)。
如果一方不接收来自另一方的MSS值,则MSS就定为默认值536字节(这个默认值允许20字节的IP首部和20字节的TCP首部以适合576字节IP数据报)。
当TCP发送一个SYN时,它将能MSS值设置为外出接口上的MTU长度减去固定的IP首部和TCP首部长度。

2.3 复位报文段

TCP首部中的RST比特是用于“复位”的。一般来说,无论何时一个报文段发送出现错误,TCP都会发出一个复位报文段。
(1)到不存在的端口的连接请求
产生复位的一种常见情况是当连接请求达到时,目的端口没有进程正在听。
(2)异常终止一个连接
有可能发送给一个复位报文段而不是FIN来中途释放一个连接,称这为异常释放。Socket API通过“linger on close”选项(SO_LINGER)提供了这种异常关闭的能力。

2.4 检测半打开连接

如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这一的TCP连接称为半打开(half-open)。
任何一端的主机异常都可能导致发生这种情况。只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。

2.5 同时打开

两个应用程序同时彼此执行主动打开的情况是可能的,尽管发生的可能性极小。每一方必须发送一个SYN,且这些SYN必须传递给对方。这称为同时打开(simultaneous open)
TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接。
两端几乎在同时发送SYN并进入SYN_SENT状态。当每一端收到SYN时,状态变为SYN_RCVD,同时它们都再发SYN并对收到的SYN进行确认。当双方都收到SYN及相应的ACK时,状态都变迁为ESTABLISHED。
如图:

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

 

 

3. TCP连接终止

3.1 连接终止

建立一个连接需要三次握手,而终止一个连接要经过4次握手。这由TCP的半关闭(half-close)造成的。
既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端已经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果。
收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据,而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的TCP应用程序这样做。
首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这个FIN)执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭。

TCP客户端发送给一个FIN,用来关闭从客户端到服务器的数据传送,当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。同时TCP服务器还向应用程序传送一个文件结束符。接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN,客户必须发回一个确认,并将确认序号设置为收到序号加1。

如图,三次握手和四次挥手:

3.2 TCP半关闭

TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓的半关闭。
如果应用程序不调用close而调用shutdown,则Socket API支持半关闭。然而,大多数的应用程序通过调用close终止两个方向的连接。

 3.3 2MSL等待状态

TIME_WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。
它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为TCP报文段以IP数据包在网络内传输,而IP数据包则有限制其生存时间的TTL字段。
当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的Socket不能再被使用。这个连接只能在2MSL结束后才能再被使用。某些实现和API提供了一种避开这个限制的方法。使用Socket API时,可说明其中的SO_REUSEADDR选项。它将让调用者对处于2MSL等待的本地端口进行赋值,但我们将看到TCP原则上仍将避免使用处于2MSL连接中的端口。

3.4 FIN_WAIT_2状态 

在FIN_WAIT_2状态已经发出了FIN,并且另一端也已对它进行确认。除非实行半关闭,否则将等待另一端的应用层意识到它已收到一个文件结束符说明,并向我们发一个FIN来关闭另一方向的连接。只有当另一端的进程完成这个关闭,我们这端才会从FIN_WAIT_2状态进入TIME_WAIT状态。
这意味着我们这端可能永远保持这个状态,另一端也将处于CLOSE_WAIT状态,并一直保持这个状态直到应用层决定进行关闭。
如何防止这种FIN_WAIT_2状态的无限等待?
如果执行主动关闭的应用层将进行全关闭,而不是半关闭来说明它还想接收数据,就设置一个定时器。如果这连接空闲超时,则TCP将进入CLOSED状态。

3.5 同时关闭 

双方都执行主动关闭是可能的,TCP协议也允许这样的同时关闭。
当应用层发生关闭命令时,两端均从ESTABLISHED变为FIN_WAIT_1这将导致双方各发送一个FIN,两个FIN经过网络传送后分别达到另一端。收到FIN后,状态有FIN_WAIT_1变为CLOSEING,并发送最后的ACK。当收到最后的ACK时,状态变为TIME_WAIT。同时关闭与正常关闭使用的报文段交换数目相同。

4. TCP服务器设计

TCP服务器进程是并发的,当一个新的连接请求到达服务器时,服务器接受这个请求,并调用一个新进程来处理这个新的客户请求。

4.1 TCP服务器端口号

处于LISTEn状态的服务器进程是当前服务器用于接收客户连接请求,当传入的连接请求达到并被接收时,系统内核中的TCP模块旧创建一个处于ESTABLISHED状态的进程。

4.2 呼入连接请求队列

一个并发服务器调用一个新的进程来处理每个客户请求,因此处于被动连接请求的服务器应该始终准备处理下一个呼入的连接请求。那正是使用并发服务器的根本原因。但仍有可能出现当服务器在创建一个新的进程时或操作系统正忙于处理优先级更高的进程时,到达多个连接请求。当服务器正处于忙时,TCP是如何处理这些呼入的连接请求?
(1)正等待连接请求的一端(服务器)有一个固定长度的连接队列,该队列中的连接已被TCP接受(即三次握手已经完成),但还没有被应用层锁接受。
(2)应用层将指明该队列的最大长度,这个值通常称为积压值(backlog)。积压值说明的是TCP监听的端点已被TCP接受而等待应用层接受的最大连接数。它的取值范围是0~5之间的整数,包括0和5(大多数的应用程序都将这个值说明为5)。这个积压值对系统所允许的最大连接数或并发服务器所能并发处理的客户数并无影响。(系统最大连接数和并发是应用层面的,而backlog是TCP层面的概念)
(3)当一个连接请求(即SYN)到达时,TCP使用一个算法,根据当前连接队列中的连接数来确定是否接收这个连接。
(4)如果对于新的连接请求,该TCP监听的端点的连接队列中还有空间。TCP模块将对SYN进行确认并完成连接的建立。但应用层只有在三次握手中的第三个报文段收到后才会知道这个新连接。另外,当客户进程的主动打开成功但服务器的应用层还不知道这个新的连接时,它可能会认为服务器进程已经准备好接收数据了(如果发生这种情况,服务器的TCP将接收的数据放入缓冲队列)。
(5)如果对于新的连接请求,连接队列中已没有空间,TCP将不理会收到的SYN,也不发回任何报文段(即不发回RST)。如果应用层不能及时接受已被TCP接受的连接,这些连接可能占满这个连接队列,客户的主动打开最终将超时。通常队列满是由于应用程序或OS忙造成的,这样可防止应用程序对传入的连接进行服务。由于服务器不应答SYN,迫使客户端TCP随后重传SYN,以等待连接队列由空间接受新的连接。

 

posted @ 2019-03-11 20:33  小路不懂2  阅读(437)  评论(0编辑  收藏  举报