TCP状态转换详解

一、TCP的状态转换图示

状态转换图解

1.1、TCP标志位

  • SYN:置1时用来发起一个连接;
  • ACK:置1时表示确认号为合法,为0的时候表示数据段不包含确认信息,确认号被忽略;
  • PSH:置1时请求的数据段在接收方得到后就可直接送到应用程序,而不必等到缓冲区满时才传送;
  • FIN:置1时表示发端完成发送任务。用来释放连接,表明发送方已经没有数据发送了;
  • RST:置1时重建连接。如果接收到RST位时候,通常发生了某些错误;
  • URG:紧急指针,告诉接收TCP模块紧要指针域指着紧要数据;

1.2、TCP的状态含义

  • CLOSED:虚拟的起始点,在连接超时或者关闭时候进入此状态,这并不是一个真正的状态,而是一个假想的起点和终点;
  • LISTEN:表示服务器端的某个 SOCKET 处于监听状态,可以接受客户端的连接;
  • SYN_SENT:表示客户端已经发送了SYN报文。当客户端 SOCKET 执行 connect() 进行连接时,它首先发送 SYN 报文,然后随即进入到 SYN_SENT 状态,该状态
  • SYN_RCVD:表示服务器已经接收到了 SYN 报文,并且已经向客户端发送了SYNACK报文。在正常情况下,这是服务器端的一个短暂的中间状态,基本上用 netstat 很难看到这种状态。当 TCP 连接处于此状态时,再收到客户端的ACK 报文,它就会进入到 ESTABLISHED 状态;
  • ESTABLISHED:表示 TCP 连接已经成功建立,数据可以进行传输;
  • FIN_WAIT_1:表示主动关闭连接的一方已经向对方发送了FIN报文;
  • FIN_WAIT_2:表示主动关闭连接的一方已经收到了对方发送的ACK报文。该状态有时可以用 netstat 看到;
  • CLOSE_WAIT:表示被动关闭连接的一方已经收到了对方发送的FIN报文,并且自己已经发送了一个ACK报文给对方。接下来需要检查自己是否还有数据要发送给对方,如果没有的话就可以执行 close() 关闭这个 SOCKET 并发送 FIN 报文给对方,即关闭自己到对方这个方向的连接。有数据的话依据具体的策略(继续发送或者丢弃)去执行;
  • CLOSING:表示主动关闭连接的一方收到了对方发送的FIN报文,并没有收到对方的ACK报文,表示双方都正在关闭 SOCKET 连接。这种状态比较少见,但当双方几乎在同时 close() 一个 SOCKET 的话,就出现了双方同时发送 FIN 报文的情况,这时就会出现 CLOSING 状态;
  • LAST_ACK:表示被动关闭连接的一方已经发送了FIN报文,正在等待对方的ACK报文;
  • TIME_WAIT:表示主动关闭的一方已经收到了对方的FIN报文,并且也已经发送出了ACK报文。TIME_WAIT状态下的 TCP 连接会等待 2*MSLMax Segment Lifetime,最大分段生存期,指一个 TCP 报文在 Internet 上的最长生存时间。每个具体的 TCP 协议实现都必须选择一个确定的 MSL 值,RFC 1122 建议是 2 分钟,但 BSD 传统实现采用了 30 秒,Linux 可以 cat /proc/sys/net/ipv4/tcp_fin_timeout 看到本机的这个值);

二、TCP的握手与挥手

TCP通信过程

三、一些问题

3.1、为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

服务端的 LISTEN 状态下的 SOCKET 当收到 SYN 报文的连接请求后,它可以把 ACK 和 SYN(ACK 起应答作用,而 SYN 起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的 FIN 报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭 SOCKET,也即你可能还需要发送一些数据给对方之后,再发送 FIN 报文给对方来表示你同意现在可以关闭连接了,所以它这里的 ACK 报文和 FIN 报文多数情况下都是分开发送的。

3.2、为什么 TIME_WAIT 状态还需要等 2MSL 后才能返回到 CLOSED 状态?

  • **可靠的实现 TCP 全双工连接的终止:**TCP 协议在关闭连接的四次握手过程中,最终的 ACK 是由主动关闭连接的一端(后面统称 A 端)发出的,如果这个ACK 丢失,对方(后面统称 B 端)将重发出最终的 FIN,因此 A 端必须维护状态信息(TIME_WAIT)允许它重发最终的 ACK。如果 A 端不维持 TIME_WAIT状态,而是处于 CLOSED状态,那么 A 端将响应 RST 分节,B 端收到后将此分节解释成一个错误。因而,要实现 TCP 全双工连接的正常终止,必须处理终止过程中四个分节任何一个分节的丢失情况,主动关闭连接的 A 端必须维持 TIME_WAIT 状态 。

  • **允许老的重复分节在网络中消逝(避免同一端口对应多个套接字):**TCP 分节可能由于路由器异常而迷途,在迷途期间,TCP 发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个迟到的迷途分节到达时可能会引起问题。在关闭前一个连接之后,马上又重新建立起一个相同的 IP 和端口之间的新连接,前一个连接的迷途重复分组在前一个连接终止后到达,而被新连接收到了。为了避免这个情况,TCP 协议不允许处于 TIME_WAIT 状态的连接启动一个新的可用连接,因为 TIME_WAIT 状态持续 2MSL,就可以保证当成功建立一个新 TCP 连接的时候,来自旧连接重复分组已经在网络中消逝;

3.3、关闭 TCP 连接一定需要四次挥手吗?

不一定,四次挥手关闭 TCP 连接是最安全的做法。但在有些时候,我们不喜欢 TIME_WAIT 状态(如当 MSL 数值设置过大导致服务器端有太多TIME_WAIT 状态的 TCP 连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置 SOCKET 变量的 SO_LINGER 标志来避免 SOCKET 在 close() 之后进入 TIME_WAIT 状态,这时将通过发送 RST 强制终止 TCP 连接(取代正常的 TCP 四次握手的终止方式);

Author: bugwz
Link: https://bugwz.com/2018/05/20/tcp-state-transition/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.