TCP连接状态转换的几种异常情况

  • Post author:
  • Post category:其他




1. TCP连接:

TCP是基于连接的传输协议,TCP的可靠传输需要建立在一对一的连接的基础之上。

TCP通过三次握手建立连接,通过四次挥手断开连接。


三次握手不仅完成连接的建立,同时完成了交换初始序列号(Initial Sequence Number,ISN)。


通常关闭连接是由应用程序发起(调用close),然而一些服务器(例如Web服务器)在对请求作出响应之后也会发起关闭操作。

TCP使用一个四元组来唯一的表示一条TCP连接(source_ip、dest_ip、source_port、dest_port)。



2. TCP头部格式:

在这里插入图片描述



3. TCP连接的11种状态转换:

在这里插入图片描述



4. TCP三次握手过程中的状态转换:

第一种情况:正常打开
第二种情况:同时打开
第三种情况:连接建立超时 或 RST复位



4.1 第一种情况:正常打开:


握手过程:


在这里插入图片描述


对应的TCP状态转换:


在这里插入图片描述



4.2 第二种情况:同时打开:


说明:


关键点:


在 SYN_SENT 状态下收到 SYN报文段,而不是 SYN+ACK 报文段


“同时打开”与“正常连接”过程的区别在于:

① 正常过程中,主动打开一方发送SYN后进入SYN_SENT状态,此时等待被动打开一方回复SYN+ACK,主动打开一方在收到回复后转入ESTABLISHED:

SYN_SENT  --(SYN+ACK)-->  ESTABLISHED

② “同时打开”过程中,主动打开一方发送SYN后进入SYN_SENT状态,在SYN_SENT状态下并未等来被动打开一方的SYN+ACK,而是只有一个单独的SYN,由此判断对方也调用了主动打开,主动打开一方此时进入SYN_RCVD状态(相当于在LISTEN状态下收到SYN):

SYN_SENT  --(SYN)-->  SYN_RCVD


TIPS:


“同时打开”是一种非常特殊的场景,正常环境下,服务器是不会主动向客户端发起连接的。

“同时打开”是TCP协议栈支持的情况,如果应用程序要支持“同时打开”,则必须在代码中既调用connect(发起SYN),又调用accept(连接建立后创建关联连接的fd),既是一个“客户端”,也是一个“服务器”。


握手过程:


在这里插入图片描述


对应的TCP状态转换图:

在这里插入图片描述



4.3 第三种情况:连接建立超时 或 RST复位:


说明:


主动打开方(客户端)发出的 SYN 报文段石沉大海,无人响应,当SYN达到最大重传次数(net.ipv4.tcp_syn_retries)后,则会连接超时,TCP状态从 SYN_SENT 状态重新传入 CLOSED 状态;

或者,客户端调用 connect 发起连接后又迅速调用了 close,TCP状态也会重新转入 CLOSED状态。

(注意:这种情况是SYN报文段根本无人接收,导致客户端收不到任何关于SYN的响应。例如服务器已经关闭。)

另外一种情况,是服务器收到了客户端的 SYN 报文段,但连接信息有误,例如申请连接的端口号无人监听,此时会回复 RST 复位报文段,用于快速重置连接。

//client:
CLOSED -->  SYN_SENT  --(SYN timeout || close || RST)-->  CLOSED

同样的,服务端发出的 SYN+ACK 报文段同样有可能会因达到最大重传次数而超时(net.ipv4.tcp_synack_retries),此时服务器状态会由 SYN_RCVD 重新转回 LISTEN 状态。

//server:
CLOSED -->  LISTEN  -->  SYN_RCVD  --(SYN+ACK timeout || close || RST)-->  LISTEN


对应的TCP状态转换图:

在这里插入图片描述



5. TCP四次挥手过程中的状态转换:

第一种情况:正常关闭
第二种情况:同时关闭
第三种情况:四次挥手合并成三次挥手



5.1 第一种情况:正常关闭:


挥手过程:


在这里插入图片描述


对应的TCP状态转换图:


在这里插入图片描述



5.2 第二种情况:同时关闭:


说明:


关键点:


在 FIN_WAIT_1 状态下收到 FIN报文段,而不是ACK报文段


“同时关闭”与“正常关闭”过程的区别在于:

① 在 正常关闭 的过程中,主动发起关闭的一方在发送 FIN报文段后,由 ESTABLISHED 状态进入到 FIN_WAIT_1 状态,并等待对方回复ACK,在收到ACK后随即转入 FIN_WAIT_2 状态:

//client:
ESTABLISHED  --(FIN)-->  FIN_WAIT_1  --(rcv ACK)-->  FIN_WAIT_2

② 在 同时关闭 的过程中,主动发起关闭的一方 FIN_WAIT_1 状态下并未收到 ACK报文段,而是收到了对方的FIN,说明对方也主动调用了close关闭连接,此时则由 FIN_WAIT_1 状态转入 CLOSING 状态,并回复ACK:

//client:
ESTABLISHED  --(FIN)-->  FIN_WAIT_1  --(rcv FIN)-->  CLOSING


TIPS:


在“同时关闭”的过程中引入了一个独有的TCP状态:

CLOSING



在“同时打开”的场景下并未引入新的TCP状态,而是直接复用了 SYN_RCVD(由 SYN_SENT 转入 SYN_RCVD,而非转入ESTABLISHED),这是因为SYN_RCVD有“回复ACK并等待对方回复ACK”的功能,而在连接“正常关闭”的流程中并没有这样的一个状态,因此引入“CLOSING”状态,用于“回复ACK并等待ACK”。

另外需要注意的是:在“同时关闭”的场景下同样需要 TIME_WAIT 状态。


挥手过程:


在这里插入图片描述


对应的TCP状态转换图:

在这里插入图片描述



5.3 第三种情况:四次挥手合并成三次挥手:


说明:


四次挥手中的FIN和ACK可能会合并发送,此时主动关闭的一方会直接从 FIN_WAIT_1 状态转入 TIME_WAIT 状态:

FIN_WAIT_1  --(rcv FIN+ACK)-->  TIME_WAIT


注意:


在服务器端(被动关闭一方),

应用程序层面并不会合并发送 FIN + ACK

,应用程序需要处理客户端的FIN并首先回复ACK,然后才会检测本端无数据需要发送后才会发送FIN。

但是

在内核协议栈层面,可能会有ACK延迟确认,导致ACK等到FIN后合并发送


挥手过程:


在这里插入图片描述


对应的TCP状态转换图:


在这里插入图片描述



6. TCP连接状态下的异常处理:



6.1 TCP处于连接状态下的两种异常:

  1. 应用进程异常退出;
  2. 主机系统异常退出。
  • 对于第一类

    应用进程异常退出

    (例如进程被kill掉 或者 访问空指针等运行异常退出),

    Linux内核可以检测到进程终止

    ,此时进程占用的系统资源(fd、内存等)都会被回收,同时内核会完成TCP的四次挥手流程(TCP协议栈是在内核实现的),但与进程正常调用close关闭连接不同的是,进程异常退出时系统不会等待TCP发送缓冲区的数据全部发完,而是会直接发送FIN报文段,通知对端关闭TCP连接。
  • 对于第二类情况

    主机系统异常退出

    (例如主机宕机 或者 网线被拔),此时内核一并挂掉,来不及进入TCP四次挥手流程,所以对端是无法感知到发送端的情况的,对端只能等到TCP保活机制超时后断开连接(默认配置下2小时11分钟)。如果主机迅速恢复或者重新插上网线,连接会恢复到异常之前的状态,对端同样无法感知。



6.2 进程异常退出与TIME_WAIT处理:

当TCP连接中的服务端进程异常退出时,

Linux内核可以检测到进程崩溃

,此时内核会自动进入四次挥手流程关闭与客户端之间的连接。

由于此时服务端扮演的是主动发起四次挥手的角色,所以服务端最后会进入到

TIME_WAIT

状态,此时就引入了一个新的异常边界问题:

当崩溃的

服务器进程

快速完成重启,并尝试绑定LISTEN监听端口时,由于此时端口、fd等资源还在被之前的连接占用(TIME_WAIT状态中),服务器将无法完成bind绑定。

为了让服务器进程可以快速完成服务恢复,对

监控端口的TIME_WAIT状态

的处理方法是:

在socket之后、bind之前,设置套接字选项

SO_REUSEADDR

,该选项表示:如果bind要绑定的端口正被占用且处于TIME_WAIT 状态,则可以重用端口。此时即可绑定成功。

SO_REUSEADDR的使用方法:

//listenfd = socket(AF_INET, SOCK_STREAM, 0);

int optval = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

//bind(listenfd, &servaddr);



版权声明:本文为ArtAndLife原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。