TCP State Transition

TCP(Transmission Control Protocol) 是由 IETF 的 RFC 793 定义的一种面向连接的、可靠的、基于字节流的传输层通信协议。

TCP 报文段

TCP 数据被封装在一个 IP 数据报中,如下图所示:

下图是 TCP 首部的数据格式,如果不计任选字段,它通常是20个字节:

下面介绍重要的几个数据:

  • 32位序号:表示数据当前发送的第一个字节在字节流中的序号
  • 32位确认号:表示发送端所期望收到的下一个序号,因此该序号位上一次收到的序号加一
  • 6个特殊标志bit: (按照排列顺序)
    • URG: 紧急指针有效
    • ACK:确认序号有效
    • PSH:接收方应该尽快将这个报文段交给应用层
    • RST:重建连接
    • SYN:同步序号,用来发起一个连接
    • FIN:发送端完成任务,关闭发送端到接收端连接

其余的解释请参考 TCP/IP 协议详解。

TCP 连接的状态图

tcp state

TCP 连接的建立与终止

TCP 是一个面向连接的通信协议,这要求通信双方在进行通信之前,需要先建立其连接。在常见的客户端、服务器模式的程序中,通常是服务器绑定端口,并在该端口上监听客户端连接请求;客户端主动向服务器发起连接请求,待服务器响应后,双方建立起一条通信链路。

建立

TCP 连接建立时通信双方的分组报文如下图所示:

如图所示,客户端调用connect,此时客户端发送SYN报文;服务端调用accept接受该连接请求,同时发送SYN和ACK;等到客户端响应了ACK后,双方建立起完整连接。

将上述过程映射到TCP状态图上进行观察,在服务器端:

  • 刚开始服务器处于CLOSED状态。
  • 服务器初始化时绑定了具体的端口,并调用 listen监听该端口,进入了LISTEN状态。
  • 服务端接收到了来自客户端的SYN报文,发送SYN和ACK给客户端,然后进入SYN_RCVD状态。
  • 当服务端接收到了客户端发送的ACK时,进入ESTABLISHED状态。

客户端方面:

  • 开始同样处于CLOSED状态。
  • 应用主动调用connect发起连接,发送SYN报文给服务器,然后进入 SYN_SEND状态。
  • 当收到服务器的SYN和ACK后,发送对应的ACK给服务器,并进入ESTABLISHED状态。
  • 当双方都进入ESTABLISHED状态时,表示连接已经建立成功。

如果,客户端在发送了SYN报文后,等待超时,重试几次后,便会触发 Timeout进入CLOSED ,在应用层则表示为connect函数调用失败。

关闭连接

FIN报文用于通知对方关闭本方向的连接。由于TCP是一个全双工的通信协议,像管道一样,支持关闭某一方向上的连接,所以在TCP中关闭连接需要双方都发送FIN报文。此时分组报文如下图所示:

当某一方关闭连接时,发送FIN报文给另一方,对方回复ACK,同时也发送 FIN报文;等到双方都收到最后的ACK后,连接关闭。当然,如果另一方只回复了ACK而没有发送FIN报文,则表示对方仍然想要发送数据,这种情况称为TCP的半关闭。只有当双方都发送了FIN并接收到对方的ACK后,才算真正的连接关闭。所以上图中Server端的FIN报文可以在接收到Client的FIN报文后,隔一段时间再发送。

在状态图中对应了主动关闭和被动关闭,首先观察主动关闭:

  • 当应用调用close后,发送FIN报文给对方,并由ESTABLISHED状态进入FIN_WAIT_1状态。
  • 收到ACK报文后,进入FIN_WAIT_2状态。
  • 等待对方的FIN报文到达,并发送ACK给对方,进入TIME_WAIT状态。
  • 如果在 FIN_WAIT_1状态直接接收到FIN和ACK,则直接进入TIME_WAIT状态。
  • TIME_WAIT状态等待了2 MSL后,进入CLOSED状态,此时连接关闭。

被动关闭则简单得多:

  • 当收到对方的FIN报文后,发送ACK并由ESTABLISHED进入 CLOSE_WAIT状态。
  • 用户层调用close后,发送FIN报文同时进入LAST_ACK状态。
  • 接收到对方的 ACK 后,进入CLOSED状态,连接关闭。

TIME_WAIT状态可能时状态图中最不易懂的地方,它也被称为 2 MSL 状态。每一个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),表示任何报文段被丢弃前能在网络中存活的时间。当TCP执行主动关闭并发送了ACK给对方进入TIME_WAIT状态后,该连接必须在TIME_WAIT状态停留2倍的MSL。这样可以保证TCP在超时后再次发送最后的ACK报文以防止这个ACK报文丢失。使用2 MSL的另外一点是,当前的socket关闭后,可能立即被用于建立另一个TCP连接,而网络中可能存在着尚未到达具有TIME_WAIT状态一方的包,需要保证这些包不会影响到接下来即将建立的连接。2 MSL的时间间隔中不允许socket被重新使用,同时也能够保证消耗掉网络中的包。所以TIME_WAIT状态存在有两个理由:

  • 可靠地实现 TCP 全双工连接的终止
  • 允许老的重复的包在网络中消逝

关于LAST_ACK状态和TIME_WAITE状态,下面假设:主动关闭的一端为A,被动关闭的一端为B,根据B是否收到最后的ACK包分为两种情况:

  1. B发送FIN,进入LAST_ACK状态,A收到这个FIN包后发送ACK包,B收到这个ACK包,然后进入CLOSED状态
  2. B发送FIN,进入LAST_ACK状态,A收到这个FIN包后发送ACK包,由于某种原因,这个ACK包丢失了,B没有收到ACK包,然后B等待ACK包超时,又向A发送了一个FIN包
    • 假如这个时候,A还是处于TIME_WAIT状态(也就是TIME_WAIT持续的时间在2 MSL内)。A收到这个FIN包后向B发送了一个ACK包,B收到这个ACK包进入CLOSED状态
    • 假如这个时候,A已经从TIME_WAIT状态变成了CLOSED状态。A收到这个FIN包后,认为这是一个错误的连接,向B发送一个RST包,当B收到这个RST包,进入CLOSED状态
    • 假如这个时候,A挂了(假如这台机器炸掉了)。B没有收到A的回应,那么会继续发送FIN包,也就是触发了TCP的重传机制,如果A还是没有回应,B还会继续发送FIN包,直到重传超时(至于这个时间是多长需要仔细研究),B重置这个连接,进入CLOSED状态,参考链接看这里

同时关闭

如TCP同时打开一样,TCP也存在同时关闭状态,此时双方均进入FIN_WAIT_1状态,并再接收到FIN后进入CLOSING状态。等到接收到ACK后,则进入TIME_WAIT状态。

TCP 复位

在TCP首部中RST位表示复位,用来关闭异常的连接,在TCP的设计中它是不可或缺的。发送RST包关闭连接时,不必等缓冲区的包都发出去,直接就丢弃缓存区的包发送RST包。而接收端收到RST包后,也不必发送ACK包来确认。

下面来分析一下TCP中RST包出现的主要场景。

到不存在的端口的连接请求

产生复位的一种常见情况是当连接请求到达时,目的端口没有进程在监听。例如,A向B发起连接,但B之上并未监听相应的端口,这时B操作系统上的 TCP处理程序会发RST包。

异常终止一个连接

终止一个连接的正常方式是一方发送FIN,这也成为有序释放,因为在所有排队数据都已经发送之后才发送FIN,正常情况下没有数据丢失。但是也可以使用RST来直接释放一个连接,这种方式称为异常释放。使用异常终止有两个有点:

  • 丢弃任何待发送数据并立即发送复位报文段
  • RST的接收方会区分另一端是异常还是正常关闭

检测半打开连接

如果一方已经关闭或异常终止而另一方还不知道,这样的TCP连接被称为半打开的。比如系统断电而不是正常结束就可能造成半打开的连接。如果发生异常的一方重启后重新连接到远程服务,则会发生错误,此时远程服务器会发送RST关闭此连接。比如,AB正常建立连接了,正在通讯时,A向B发送了FIN包要求关连接,B发送ACK后,网断了,A通过若干原因放弃了这个连接(例如进程重启)。网通了后,B又开始发数据包,A收到后表示压力很大,不知道这野连接哪来的,就发了个RST包强制把连接关了,B收到后会出现 connect reset by peer错误。

Ref