TCP的连接管理

简述

TCP是面向连接的协议,TCP把连接作为最基本的抽象。每一条TCP连接唯一地被通信两端的两个端点所确定。那么,TCP连接的端点是什么呢?TCP连接的端点又叫套接字(socket),根据TCP协议的规定,端口号拼接到IP地址即构成了套接字,即

套接字 socket = (IP地址:端口号)

这样一来,TCP连接可以以下式子表示

TCP连接 ::= {socket1, socket2} = {(IP1: port1),(IP2: port2)}

在面向连接通信中,连接的建立和释放是必不可少的过程。TCP连接的建立采用客户服务器方式,主动发起连接建立的应用进程叫做客户,而被动等待连接的应用进程叫做服务器。
本文主要讲述TCP是如何管理连接的建立和连接的释放的。

TCP的连接建立

三次握手建立TCP连接
图1:三次握手建立TCP连接

图1画出了TCP连接建立的过程。假定图中左端是客户A,右端是服务器B,一开始时,两端都处于CLOSED(关闭)状态。图中的方框分别是端点所处的状态。
1)服务器进程准备好接受外来的连接,这通常是通过调用socket,bind,listen这三个函数来完成,我们称之为被动打开(passive open)。然后服务器进程就处于LISTEN状态,等待客户的连接请求,如有,则作出响应。
2)客户通过调用connect发起主动打开(active open),向服务器发出连接请求报文段,请求中的首部的同步位SYN = 1,同时选择一个初始序号seq = x。TCP规定,SYN报文段不能携带数据,则要消耗一个序号。
这时,TCP客户进入SYN-SEND(同步已发送)状态。

TCP规定,首部中序号字段值是本报文段所发送数据的第一个字节的序号。

3)服务器收到客户端连接请求后,必须确认(ACK)客户的SYN报文段。在确认报文段中,把SYN和ACK位都置为1,确认号为ack = x + 1,同时也为自己选择一个初始序号seq = y。请注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。
这时,TCP服务器进入SYN-RCVD(同步收到)状态。

TCP规定,若确认号 = N,则表明:到序号 N - 1为止的所有数据都已正确收到。

4)客户在收到服务器的确认后,还要向服务器进程给出确认。确认报文段的ACK置1,确认号ack = y + 1,而自己的序号seq = x + 1。TCP规定,这个报文段可以携带数据,也可以不携带数据,如果不携带数据,下一个数据报文段的序号仍是seq = x + 1。
这时,TCP连接已经建立,客户进入ESTABLISHED(已建立连接)状态。
5)服务器收到客户的确认后,也进入ESTABLISHED状态。
在上述的建立连接的过程中,前后发送了三个报文段,因此TCP建立连接的过程也称之为三次握手(three-way handshake)。

为什么需要三次握手

为什么客户在收到服务器的确认后,还要向服务器发送一次确认呢?这主要是为了防止已失效的连接请求报文段突然又传送到了服务器,因而发生错误。
考虑一种情况,客户发出连接请求后,但因连接请求报文丢失而未收到确认。于是客户再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。客户共发送了两个连接请求报文段,其中第一个丢失,第二个到达了服务器。没有“已失效的连接请求报文段”。
现假定一种异常情况。即客户发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达服务器。本来这是一个早已失效的报文段,但服务器收到此失效的连接请求后,就误认为是客户又一次发出一次新的连接请求。于是就向客户发出确认报文段,同意建立连接。假定不采用三次握手,那么只要服务器发出确认,新的连接就建立了。
由于现在客户端并没有发出建立连接的请求,因此不会理睬服务器的确认,也不会向服务器发送数据。但服务器却以为新的连接已经建立了,并一直等待客户发送数据。服务器的许多资源就这样白浪费了。
采用三次握手的办法可以防止上述现象的发生。例如刚才的情况下,客户不会向服务器的确认发出确认,由于服务器收不到确认,就知道客户并没有要求建立连接。

TCP的连接释放

TCP建立一个连接需要三个报文段,释放一个连接却需要四个报文段。
TCP释放连接的过程
图2:TCP释放连接的过程

数据传输结束后,通信的双方可以释放连接。数据传输结束后的客户A和服务器B都处于ESTABLISHED状态,然后进入释放连接的过程。
1)A的应用进程先发出释放连接报文段,并停止发送数据,主动关闭TCP连接。A把连接释放报文段首部FIN置1,其序号为seq = u。这时A进入FIN-WAIT-1(终止等待1)状态。
2)B收到连接释放报文段后即发出确认确认号为ack = u + 1,而自己的序号为seq = v。然后B就进入CLOSE-WAIT(关闭等待)状态。TCP服务器进程这时应通知高层应用进程,因而从A到B这个方向的连接就释放了,这时的TCP连接处于半关闭状态,即A已经没有数据要发送了,但B若发送数据,A仍接收。
3)A收到来自B的确认后,就进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
4)若B已经没有要向A发送的数据,其应用进程就通知TCP释放 连接。这时B发出的连接释放报文段FIN = 1,还必须重复上次已发送过的确认号ack = u + 1。假定B的序号为w(在半关闭期间B可能又发送了一些数据)。这时B就进入了LAST-ACK(最后确认)状态,等待A的确认。
5)A收到了的连接释放报文段后,必须对此发出确认。其确认号为ack = w + 1,而自己的序号为seq = u + 1。然后进入到TIME-WAIT(时间等待)状态。请注意,现在TCP连接还没有释放掉。必须经过时间等待计时器(TIME-WAIT timer)设置的时间 2MSL后,A才进入到CLOSED状态。时间MSL叫做最长报文段寿命(Maximum Segment Lifetime)。
6)B只要收到A发出的确认,就进入CLOSED状态。我们注意到,B结束TCP连接的时间要比A早一些。
由于释放TCP连接的过程需要发送四个报文段,因此释放连接的过程也称之为四次握手。

TIME_WAIT状态

上述释放连接的过程中,A在TIME-WAIT状态必须等待2MSL,才进入CLOSED状态,上面也提到,这个MSL是报文段的最长寿命。那么MSL的真实含义是什么呢?
MSL是任何IP数据报能够在网络中存活的最长时间。我们知道这个时间是有限的,因为每个数据报含有一个称为跳限(hop limit)的8位字段,它的最大值是255,即最大为255跳。尽管这是一个跳数限制而不是真正的时间限制,我们仍然假设:具有最大跳限的数据报在网络中存在的时间不可能超过MSL秒。
任何TCP实现都必须为MSL选择一个值。RFC 1122的建议值为2分钟,对于现在的网络,MSL = 2分钟可能太长了,故一些实现采用30秒的值,这意味着,TIME-WAIT状态的持续时间在1分钟到4分钟之间。
为什么客户在TIME-WAIT状态必须2MSL的时间呢?这有两个理由:
1)可靠地实现TCP全双工连接的终止
客户A最后一个ACK报文段可能丢失,这样服务器B处于LAST-ACK状态而收不到确认。接下来B会超时重传FIN + ACK报文段,而A就能在2MSL时间内收到这个重传的FIN + ACK报文段,并再重传一次确认,并重新启动2MSL计时器。最后,A和B都正常进入CLOSED状态。
如果A在发送完最后一个ACK报文段后立即释放连接,那么就无法收到B重传的FIN + ACK报文段,因而也不会再发送一次确认报文段,这样B就无法按照正常步骤进入CLOSED状态。
2)防止“已失效的连接请求报文段”出现在本连接中
客户A在发送完最后一个ACK报文段后,再经过时间2MSL,就可以使本连接持续的时间内所产生的所有报文段都会网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。

后记

有这样一道面试题,对于应用程序来说,什么情况下会出现大量 TIME_WAIT 的状态
TIME_WAIT 出现的原因可以参考上面的详细解析,从上面的描述我们也可以知道,TIME_WAIT 的出现是一般是客户主动关闭 TCP 连接而出现的,即出现在客户端机器,服务端机器一般不会出现 TIME_WAIT 状态。
那么,在什么情况下,客户端机器会大量出现关闭 TCP 连接呢?
记得在网易的时候,我曾经为处理用户连接实时语音服务的日志开发过一个日志补全进程 audiolog。
用户每天累积的实时语音日志达1千多万条,audiolog 进程在每天的固定时间(01:30)扫描语音日志,发现某些日志如果存在字段缺失的情况,audiolog 会通过发送 HTTP 请求查询并补全字段信息,audiolog会在短时间内扫描完所有的日志并运行完毕。我们知道 HTTP 是基于 TCP 的,这就导致短时间内 audiolog 断开大量的 TCP 连接,导致大量 TIME_WAIT 状态的出现。


图3:audiolog 机器出现 TIME_WAIT 状态统计

由图3可以看到,audiolog 所在的机器,在01:30这个时间点,出现了大量的 TIME_WAIT 状态,这个时间点正是 audiolog 进程运行的时间点,这跟上述分析出现大量 TIME_WAIT 状态的原因是致的。

上述的情况,都是在客户端出现 TIME_WAIT 的情况。另外,如果服务端主动关闭客户端非法请求或者处理长时间不跳跃连接,也会在服务端机器出现 TIME_WAIT 的状态。

参考资料

  1. 《计算机网络(第6版)》,谢希仁著
  2. 《UNIX网络编程 卷1:套接字联网API(第3版)》,W. Richard Stevens等著