读懂TCP状态变换的过程,对于理解网络编程颇有帮助,本文将对TCP状态转移过程进行介绍,但各个状态(总共11个)的含义不在本文介绍的范围。
内容来源:《UNIX网络编程》第一卷第二章2.6节,若是读者对某个知识点不太理解,请参考原文。
TCP状态转换图(state transition diagram)
1. 建立连接(three-way hand shake)
- 主动打开(passive open):服务器必须准备好接受外来的连接,通常通过socket、bind和listen完成。
- 被动打开(active open):客户端通过connect发起主动打开。
插句话,该文介绍的性能分析工具(sar -n TCP,ETCP 1)命令可以对主动打开和被动打开的数目进行统计,在一定程度上可以反应服务器端的繁忙程度。
下图给出客户端与服务器端的三次握手过程:
- 服务器准备好接受外来连接,通常通过socket、bind和listen完成。(服务器:CLOSED->LISTEN)
- 客户端通过connect连接服务器,客户端TCP将发送一个SYN分解,告诉服务器,客户将在待建立连接发送数据的初始序列号。(客户端:CLOSED->SYN_SENT)
- 服务器端必须ACK客户的SYN,同时发送一个SYN,告诉客户,服务器将在待建立连接发送数据的初始序列号。(服务器:SYN_RECV)
- 客户必须ACK服务器的SYN。(客户端:SYN_SENT->ESTABLISHED)
- 服务器接收到客户的ACK。(服务器:SYN_RECV->ESTABLISHED)
下图三次握手对应的状态转移图:
2. 建立连接(同时打开simultaneous open)
参考《TCP/IP详解》卷一第18章18.8节。这种情况发生在两端几乎同时发送SYN并且这两个SYN在网络中交错的情形。这种情况可能发生,但是非常罕见。这要求客户/服务双方使用“两个相同的端口”互相连接,这样双方才能主动向彼此连接。
例如,主机A中的一个应用程序使用本地端口7777,并与主机B的端口8888执行主动打开。主机B中的应用程序则使用本地端口8888,并与主机A的端口7777执行主动打开。
下图给出同时打开过程:
下图给出同时打开的状态转移图:
说明:从目前个人经验来看,这种场景没有遇到过,但从理解TCP状态转移图来说是不可避免的一部分。但《TCP/IP详解》卷一第18章18.8节中支出,许多伯克利版的TCP实现都不能正确地支持打开。
3. 建立连接失败(1)
考虑场景:客户端尚未接到服务器的ACK+SYN,异常退出。这时,当客户端再接收到来自服务器端的ACK+SYN时,客户端回复RST(reset)。服务器处于SYN_RECV状态,这是接收到客户端的RST时,则从SYN_RECV转移到LISTEN状态。
具体过程:
- 服务器准备好接受外来连接,通常通过socket、bind和listen完成。(服务器:CLOSED->LISTEN)
- 客户端通过connect连接服务器,客户端TCP将发送一个SYN分解,告诉服务器,客户将在待建立连接发送数据的初始序列号。然后客户端crash。(客户端:CLOSED->SYN_SENT,然后突然crash,则退出SYN_SENT)
- 服务器端必须ACK客户的SYN,同时发送一个SYN,告诉客户,服务器将在待建立连接发送数据的初始序列号。(服务器:SYN_RECV)
- 客户找不到ACK+SYN对应的SYN_SENT状态的socket,则响应RST。(服务器:SYN_RECV->LISTEN)
下图给出建立连接失败的状态转移图:
4. 建立连接失败(2)
考虑场景1:服务器的进程异常退出,客户端不知道。那么客户端发送SYN后,服务器端响应RST,则客户端建立连接失败。
考虑场景2:服务器的机器关闭,服务器IP不可达。那么客户端发送SYN后,超时重发,超过重试次数,最终TIMEOUT,则客户端建立连接失败。
具体过程:
- 假设服务器进程退出。(服务器:LISTEN->CLOSED)
- 客户端通过connect连接服务器,客户端TCP将发送一个SYN分解,告诉服务器,客户将在待建立连接发送数据的初始序列号。(客户端:CLOSED->SYN_SENT)
- 服务器端收到客户端SYN,响应RST(服务器:CLOSED)
- 客户收到RST。(客户端:SYN_SENT->CLOSED)
下图给出建立连接失败的状态转移图:
5. 断开连接(四次挥手)
- 主动关闭(active close):某个应用程序首先调用close,发送一个FIN包。
- 被动关闭(passive close):接收到FIN的对端执行被动关闭。
下图给出客户端与服务器的四次挥手过程:
- 某个应用程序首先调用close,该端发送一个FIN包,表示数据发送完毕,该应用程序再无更多数据发送给对端。(例如HTTP服务器发送Reponse数据给client后,再无多余数据发送,则Server可以执行主动关闭)(主动端:ESTABLISHED->FIN_WAIT_1)
- 接受到FIN的对端执行被动关闭。首先ACK这个收到的FIN包。该FIN包的接收也作为一个文件结束符(EOF)传递给应用程序(放在已排队等候该应用进程接收的任何其他数据之后),因为FIN包意味着接收端应用进程在相应的连接上再无额外数据可接收。(被动端:ESTABLISHED->CLOSE_WAIT,主动端:FIN_WAIT_1->FIN_WAIT_2)
- 一段时间后,接收到这个EOF的应用进程将调用close关闭他的socket。这导致它的TCP也发送一个FIN包。(被动端:CLOSE_WAIT->LAST_WAIT)
- 接收这个最终FIN的执行主动关闭的那一端ACK这个FIN。(被动端:LAST_WAIT->CLOSED,主动端:FIN_WAIT_2->TIME_WAIT(2MSL之后,TIME_WAIT->CLOSED))
下图给出四次挥手的状态转移图:
6. 断开连接(同时关闭simultaneous close)
参考《TCP/IP详解》卷一第18章18.9节。我们在以前讨论过一方(通常但不总是客户方)发送第一个FIN执行主动关闭。双方都执行主动关闭也是可能的, TCP协议也允许这样的同时关闭(simultaneous close)。
下图给出同时关闭过程:
下图给出同时关闭的状态转移图:
7. 断开连接(在FIN_WAIT_1状态中,接收FIN+ACK)
考虑场景:被动关闭端收到FIN包后,直接发送FIN+ACK,则主动关闭方则从FIN_WAIT_1跳过FIN_WAIT_2,直接进入TIME_WAIT。
给出具体流程:
- 某个应用程序首先调用close,该端发送一个FIN包,表示数据发送完毕,该应用程序再无更多数据发送给对端。(例如HTTP服务器发送Reponse数据给client后,再无多余数据发送,则Server可以执行主动关闭)(主动端:ESTABLISHED->FIN_WAIT_1)
- 接受到FIN的对端执行被动关闭。收到FIN包之后,被动端调用close关闭socket,则FIN+ACK同时发给主动端。(被动端:ESTABLISHED->CLOSE_WAIT->LAST_ACK,主动端:FIN_WAIT_1->TIME_WAIT)
- 接收这个最终FIN的执行主动关闭的那一端ACK这个FIN。(被动端:LAST_WAIT->CLOSED,主动端:FIN_WAIT_2->TIME_WAIT(2MSL之后,TIME_WAIT->CLOSED))
下图给出同时关闭的状态转移图:
参考:
- 《UNIX网络编程》第一卷第2章
- 《TCP/IP详解》卷一第18章
说明(重点注意):
- 《UNIX网络编程》第一卷(第三版)图2.4有个错误,服务器从LISTEN转移到SYN_RCVD时,条件应该是“接收:SYN;发送:SYN,ACK”,而不是“接收:RST;发送:SYN,ACK”。这一点可以从英文原本的配图看到。
- 我也认为,书中的TCP状态转移图中,SYN_RCVD转移到LISTEN的条件应该是服务器状态,而非客户端状态。
作者说明:
限于本人对某些技术点的理解程度(虽然也查了很多资料),可能某些地方表达的不太准确,敬请指教,联系方式为[email protected]。