在计算机网络中,传输控制协议(TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 通过三次握手建立连接,并使用一系列的队列来管理连接过程中的数据流动。在本文中,我们将深入探讨 TCP 三次握手的过程,Linux 内核的实现逻辑,以及 TCP 队列中的全连接(FULL)与半连接(SYN)队列的概念和作用。
TCP三次握手是建立一个可靠的连接的基础。在这个过程中,有两个重要的队列:半连接队列(SYN queue)和全连接队列(ACCEPT queue)。
先看下老生常谈的三次握手的过程
- 客户端发送SYN:客户端调用
connect
系统调用,内核将套接字状态设置为TCP_SYN_SENT
,并发送SYN报文。此时,内核会创建一个request_sock
结构,用于表示半连接请求。 - 服务端响应SYN-ACK:服务端收到SYN报文后,内核将状态设置为
TCP_NEW_SYN_RECV
,准备SYN-ACK报文响应客户端。这个状态是服务端特有的,表示已收到SYN但还未收到ACK。 - 客户端完成握手:客户端收到SYN-ACK后,内核将状态更新为
TCP_ESTABLISHED
,此时连接完全建立,客户端可以开始发送数据。
在Linux内核的TCP实现逻辑
在开始之前,我们先了解两个队列
半连接队列(SYN queue)
当客户端发送SYN报文,服务器接收后进入SYN_RECV状态,此时连接被放入半连接队列。
队列长度:长度由tcp_max_syn_backlog
、net.core.somaxconn
和业务中tcp
调用listen(fd, backlog)
的backlog
三者最小值决定。
全连接队列(ACCEPT queue)
当客户端发送ACK报文后,服务器将连接从半连接队列移动到全连接队列,进入ESTABLISHED状态。
队列长度:长度由net.core.somaxconn
和listen(fd, backlog)
的backlog
两者最小值决定。
具体的流程
- 当服务器执行
listen
操作时,它会初始化并配置全连接和半连接队列的容量。这一过程中,服务器会分配必要的内存资源,并设置队列的初始状态,以便能够管理和跟踪到达的连接请求。 - 客户端在执行
connect
操作时,首先会将其套接字状态设置为TCP_SYN_SENT
。随后,客户端会选择一个可用的本地端口,并构造一个SYN握手请求报文发送给服务器。同时,客户端还会启动一个重传定时器,以应对可能的网络延迟或丢包情况。 - 服务器在接收到客户端的SYN请求后,会检查其接收队列的状态。如果接收队列已满,服务器可能会拒绝该连接请求。如果队列有足够空间,服务器会发送SYN-ACK响应报文给客户端,并创建一个
request_sock
对象加入到半连接队列中。同时,服务器也会启动一个定时器,用于处理该半连接的超时情况。 - 客户端在收到服务器的SYN-ACK响应后,会停止之前设置的重传定时器,并更新其套接字状态为
TCP_ESTABLISHED
。接着,客户端会启动保活计时器,并发送ACK确认报文给服务器,完成第三次握手。 - 服务器在接收到客户端的ACK确认报文后,会从半连接队列中移除之前的
request_sock
对象,并创建一个新的sock
对象。这个新对象随后会被加入到全连接队列中,并将其状态设置为TCP_ESTABLISHED
,表示连接已完全建立。 - 最后,
accept
系统调用会从全连接队列中提取一个已建立的连接,并将其返回给用户进程,以便进行后续的数据传输操作。
每个状态可能出现的问题
在TCP连接的三次握手过程中,每个状态都有可能出现不同的问题,下面将分析每个状态可能出现的问题,并结合案例分析给出相应的排查命令。
- SYN_SENT状态:
- 问题:客户端发送了SYN报文后,如果长时间没有收到服务器的SYN-ACK回复,客户端可能会重传SYN报文或者最终超时放弃连接。
- 案例分析:可能因为网络延迟、服务器处理慢或者SYN洪水攻击导致服务器没有及时响应。
- 排查:
查看本地的TCP连接状态,检查SYN_SENT的数量是否异常。
netstat -ant或ss -ant
查看当前的系统设置
sysctl net.core.somaxconn#该参数指定了系统中所有套接字监听队列的最大长度。当一个连接请求到达时,如果所有的队列都已满,新的连接请求会被拒绝或丢弃。这个参数影响到所有类型的套接字,而不仅仅是 TCP 套接字。sysctl net.ipv4.tcp_max_syn_backlog#该参数指定了 TCP 半连接队列的最大长度。在三次握手过程中,服务器收到客户端发送的 SYN 包后,将会放置在半连接队列中等待连接建立完成。如果半连接队列已满,服务器将无法接受新的连接请求,导致客户端的连接请求被丢弃。
- SYN_RCVD状态:
- 问题:服务器收到了SYN报文并响应了SYN-ACK,但客户端没有收到或者没有正确响应ACK报文,连接无法建立。
- 案例分析:可能因为网络丢包、客户端处理延迟或者服务器端的半连接队列已满导致。
- 排查:使用
netstat -an | grep SYN_RECEIVED
查看服务器端的半连接状态,检查是否有大量SYN_RCVD状态的连接。
- ESTABLISHED状态:
- 问题:连接建立后,如果一方尝试发送数据但另一方没有响应,可能会导致连接异常。
- 案例分析:可能是因为网络故障、对方应用程序崩溃或者防火墙/安全策略阻止了数据传输。
- 排查命令:使用
netstat -an | grep ESTABLISHED
查看已建立的连接,结合lsof -i :port
检查端口上的应用程序状态。
- FIN_WAIT状态:
- 问题:当一方关闭连接(发送FIN报文)后,如果另一方没有及时响应(发送ACK报文),可能会导致连接长时间处于FIN_WAIT状态。
- 案例分析:可能是因为对方应用程序处理慢或者网络延迟导致。
- 排查命令:使用
netstat -an | grep FIN_WAIT
查看FIN_WAIT状态的连接,检查是否有异常的连接长时间未关闭。
- CLOSE_WAIT状态:
- 问题:当一方关闭连接后,另一方只是发送了ACK报文但没有关闭自己的连接,导致连接长时间处于CLOSE_WAIT状态。
- 案例分析:可能是因为对方应用程序没有正确处理关闭信号或者全连接队列已满导致。
- 排查命令:使用
netstat -an | grep CLOSE_WAIT
查看CLOSE_WAIT状态的连接,检查是否有大量的此类状态连接。
- LAST_ACK状态:
- 问题:当服务器端关闭连接后,等待客户端的最终ACK确认,如果客户端没有及时发送ACK,服务器端会长时间处于LAST_ACK状态。
- 案例分析:可能是因为客户端处理慢或者网络延迟导致。
- 排查命令:使用
netstat -an | grep LAST_ACK
查看LAST_ACK状态的连接,检查是否有异常的连接。
- TIME_WAIT状态:
- 问题:连接关闭后,为了确保最后一个ACK报文能够到达对方,会进入TIME_WAIT状态等待一段时间(2MSL),如果等待时间过长,可能会占用系统资源。
- 案例分析:可能是因为网络延迟或者系统设置不当导致。
- 排查命令:使用
netstat -an | grep TIME_WAIT
查看TIME_WAIT状态的连接,检查是否有大量的此类状态连接。
针对上述状态中可能出现的问题,都可以通过调整系统参数(如tcp_max_syn_backlog
、net.core.somaxconn
等)来优化性能,同时结合netstat
和ss
等工具进行实时监控和排查。
连接队列维度的异常
半连接队列已满:
- 问题:当半连接队列(SYN队列)已满时,服务器将无法处理新的SYN请求,导致新的连接尝试失败。
- 系统处理:可以通过调整
net.core.somaxconn
参数来增加队列的大小。此外,tcp_max_syn_backlog
参数也会影响半连接队列的长度,它定义了系统可以同时为还未完成三次握手的连接保留多少个半连接队列位置。 - 排查命令:
查看本地的TCP连接状态,检查SYN_SENT的数量是否异常
netstat -ant或ss -ant
查看当前的系统设置
sysctl net.core.somaxconn
#该参数指定了系统中所有套接字监听队列的最大长度。当一个连接请求到达时,如果所有的队列都已满,新的连接请求会被拒绝或丢弃。这个参数影响到所有类型的套接字,而不仅仅是 TCP 套接字。
sysctl net.ipv4.tcp_max_syn_backlog
#该参数指定了 TCP 半连接队列的最大长度。在三次握手过程中,服务器收到客户端发送的 SYN 包后,将会放置在半连接队列中等待连接建立完成。如果半连接队列已满,服务器将无法接受新的连接请求,导致客户端的连接请求被丢弃。
全连接队列已满:
- 问题:全连接队列(ACCEPT队列)已满意味着服务器已经建立了连接,但由于应用程序没有及时调用
accept()
系统调用来处理新的连接,导致新的连接请求无法被接受。 - 系统处理:可以通过调整
somaxconn
参数来增加全连接队列的大小。此外,tcp_abort_on_overflow
参数决定了当全连接队列溢出时,系统是丢弃ACK包(值为0)还是发送RST包给客户端(值为1)。
tcp_abort_on_overflow = 0:
含义:当全连接队列溢出时,系统不会主动向客户端发送RST包来终止连接。相反,它会简单地丢弃来自客户端的ACK包。这意味着客户端的连接请求被忽略,而不是被明确拒绝。
系统影响:客户端可能会重试连接,这可能导致网络流量增加,但不会立即终止连接尝试。服务器上的应用程序可能会继续处理已经接受的连接,但新的连接请求会被挂起,直到队列中有空间。这可能导致客户端体验到延迟或超时。
tcp_abort_on_overflow = 1:
含义:当全连接队列溢出时,系统会向客户端发送一个RST包,明确拒绝新的连接请求。这是一种更为积极的处理方式,可以立即终止客户端的连接尝试。
系统影响:客户端会收到一个错误信号,通常是`connection reset by peer`,表明连接已被服务器重置。这会导致客户端立即停止尝试连接,并可能触发重试逻辑或错误处理机制。这种方式可以减少无效的连接尝试,减轻服务器的网络流量压力,但可能会增加客户端处理错误的复杂性。
在实际应用中,选择哪种策略取决于具体的业务需求和网络环境。如果服务器能够较快地处理现有连接,或者希望避免客户端频繁重试,可能会选择tcp_abort_on_overflow = 1
。如果希望尽量减少对客户端的影响,或者认为客户端能够妥善处理超时和重试逻辑,可能会选择tcp_abort_on_overflow = 0
。
- 排查命令:排查全连接队列溢出的情况时,可以通过以下命令查看相关参数的当前值和统计信息:
sysctl net.core.somaxconn`:查看和设置`somaxconn`的值,它决定了全连接队列的最大长度。
cat /proc/sys/net/ipv4/tcp_abort_on_overflow`:查看`tcp_abort_on_overflow`的当前值。
netstat -s | grep "overflowed"`:查看全连接队列溢出的次数,这可以帮助确定问题是否正在发生,以及发生的频率。
Linux 内核相关参数优化
通过对TCP三次握手过程中的队列管理以及可能出现的问题的深入分析,我们可以更好地理解网络连接的建立与维护。掌握这些知识对于SRE来说至关重要,因为它们直接影响到服务器的性能和稳定性。在面对连接队列溢出或其他网络异常时,能够迅速定位问题并采取有效措施进行处理,是确保服务质量和用户体验的关键。