TCP状态转移

TCP状态转移

在前一篇文章已经介绍了TCP协议的三次握手和四次挥手。总的来说,TCP通信过程包括三个步骤:建立TCP连接(三次握手)、数据传输、终止TCP连接(四次挥手)。但是在这个通信过程中,有非常复杂的状态问题,下面就来了解一下进行TCP协议通信时候的状态转移。

TCP协议根据连接时接收到报文的不同类型,采取相应动作也不同,还要处理各个状态的关系,如当收到握手报文时候、超时的时候、用户主动关闭的时候等都需要不一样的状态去采取不一样的处理。在LwIP中,为了实现TCP协议的状态描述,定义了11种连接时候的状态:

static const char *const tcp_state_str[] = {
  "CLOSED",     //关闭状态(无连接)
  "LISTEN",     //监听状态
  "SYN_SENT",   //已发起请求连接(等待确认)
  "SYN_RCVD",   //已收到请求连接
  "ESTABLISHED",//稳定连接状态
  "FIN_WAIT_1", //单向请求终止连接状态
  "FIN_WAIT_2", //对方已应答请求终止连接
  "CLOSE_WAIT", //等待终止连接
  "CLOSING",    //两端同时关闭  
  "LAST_ACK",   //服务器等待对方接受关闭
  "TIME_WAIT"   //关闭成功(2MSL等待状态)
};
  • LISTEN:表示监听状态。服务器调用了listen函数进入监听状态,客户端可以开始进行连接了。
  • SYN_SENT:表示客户端已经发送了SYN报文请求连接(同时在等待服务器的确认)。当客户端调用connect函数发起连接时,首先发SYN给服务端,然后自己进入SYN_SENT状态,并等待服务端发送ACK+SYN报文(握手应答报文)进行确认。
  • SYN_RCVD:在每一个 TCP 连接建立时,都要进行三次握手,这个状态表示服务器接收到客户端发来的同步报文段(第一次握手),并且向客户端发送了确认同步报文段(第二次握手)之后的状态,在这个状态时,其实连接已经经历了两次握手。
  • ESTABLISHED:这个状态是处于稳定连接状态,建立连接的TCP协议两端的主机都是处于这个状态,它们相互知道彼此的窗口大小、序列号、最大报文段等信息。
  • FIN_WAIT_1FIN_WAIT_2:处于这个状态一般都是客户端主机单向请求终止连接,然后主机等待服务器的回应,而如果服务器产生应答,则主机状态转移为FIN_WAIT_2,此时<客户端 -> 服务器 >方向上的TCP连接就断开,但是<服务器 -> 客户端>方向上的连接还是存在的。此处有一个注意的地方:如果主机处于FIN_WAIT_2状态,说明主机已经发出了FIN报文段,并且服务器也已对它进行确认,除非客户端是在实行半关闭状态,否则将等待服务器主机的应用层处理关闭连接,因为服务器已经意识到它已收到FIN报文段,它需要发一个 FIN 来关闭<服务器 -> 客户端>方向上的连接。这样客户端这端才会从FIN_WAIT_2状态进入TIME_WAIT状态。如果是网络不好或者是服务器不发送FIN报文段的时候,这意味着客户端这端可能永远保持这个FIN_WAIT_2状态,从而无法 进入CLOSE_WAIT状态,并一直占用这个端口连接或者socket,在嵌入式中,如果存在多个这种状态的话,则这很可能导致内存耗尽。
  • CLOSE_WAIT: 在收到客户端主动断开连接的 FIN 报文段(第一次挥手)后,服务器返回给客户端确认报文段(第二次挥手)后的状态。
  • TIME_WAIT状态:TIME_WAIT状态也称为 2MSL等待状态

具体见下图:

  1. 红色虚线:表示服务器的状态转移。
  2. 黑色实线:表示客户端的状态转移。
    TCP状态转移_第1张图片

RST

顺便再提一点不太常见的TCP协议状态转移,主要是针对服务器端的(绿色那条):

  1. 服务器在收到SYN握手报文后,再收到了客户端的RST报文,那么它会重新进入监听状态,再重新等待连接。

一般说来,无论何时一个报文段发往基准的连接出现错误, TCP都会发出一个复位报文段(这里提到的基准的连接是指由目的 IP地址、目的端口号、源 IP地址和源端口号都是已知的连接。

此外产生复位的另一种常见情况是当连接请求到达时,目的端口并没有在监听中,当一个数据报到达目的端口时,它将产生一个ICMP端口不可达的信息,同时TCP协议将进行复位,当然啦,在lwip中这些ICMP端口不可达报文都会被丢弃的,也不用管那么多。

TIME_WAIT

第一次看这个转移图的时候,可能很多人都有疑惑,为什么要有一个 TIME_WAIT 状态?为什么不能直接到达 CLOSED 状态?

每个具体TCP连接的实现必须选择一个TCP报文段最大生存时间MSL(Maximum Segment Lifetime),就如IP数据报中的TTL字段表示报文在网络中生存的时间一样。MSL是任何报文段被丢弃前在网络内的最长时间,这个时间是有限的,为什么需要等待呢?我们知道IP数据报是不可靠的,而TCP报文段是封装在IP数据报中,TCP协议必须保证发出的ACK报文段是正确被对方接收, 因此处于该状态的主机必须在这个状态停留最长时间为2倍的MSL,以防最后这个ACK丢失,因为TCP协议必须保证数据能准确送达目的地。

假设没有 TIME_WAIT 这种状态。现实中,网络环境不是理想的。在数据包传输的过程中,难免会有一些延时啊、丢包啊的情况发生。如果在客户端的最后一个确认报文段发出去之后,由于某种原因,没有到达服务端,服务端在超时后,就会向客户端重新发一个 FIN 报文段,请求重传这个已经丢失的确认报文段。但由于在客户端,连接实际上已经断开,端口已经关闭。那么在客户端收到这个报文段后,会向服务端发送一个 RST 报文段请求重连(这也是为什么我要在前面讲解RST的原因 ),而此时服务器收到这个 RST报文段后,会认为是错误的,因为在服务器看来都没断开连接,它所期望收到的是确认报文段。所以这个时候客户端是不允许直接CLOSE关闭了事的,因此它需要等待服务器确认了,再CLOSE

再假设一下:如果没有 TIME_WAIT 这种状态,客户端在关闭连接后,再次成功建立新的连接,客户端任然可能会收到服务器的最后一个确认报文段,但是由于序号不同(重新建立连接时的序号是随机的,这点很重要,要记住),客户端会要求服务端重传数据包,这样,连接就必然会混乱出错。而在 TIME_WAIT 这种状态等待一段时间是为了让本次连接的时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的报文。

TIME_WAIT 状态的等待时间一般是 2MAL ,并且客户端连接的端口没有释放,这样,让前一个连接的报文段有足够的时间被处理或者丢弃,也就不会出现这个问题。

这才是TCP协议优雅且可靠的终止连接方式啊!太强大了,我得膜拜一下~

你可能感兴趣的:(LwIP,杰杰开源社区)