在 TCP 协议中,在客户端和服务端通信之前,两端需要建立一个 TCP 连接。服务端首先需要进入监听状态以准备迎接连接请求(通过调用 listen()),而客户端则需要发出连接请求来启动握手协议。

协议由三部分组成(见图~\ref{tcp:fig:syn})。首先,客户端发送一个叫 SYN 的特殊包给服务端,它使用一个随机产生的数字 x 作为初始序列号。因为 TCP 头部的 SYN 控制位被置为 1,所以这个包叫做 SYN 包。第二,服务端收到包之后会回复一个 SYN+ACK 的包(SYN 和 ACK 位都被置为1)。服务端选择它自己随机生成的数字 y 作为初始序列号。第三,客户端收到这个包之后发出一个 ACK 包结束这次握手。
 
SYN
 
当服务端收到初始的 SYN 包(图~\ref{tcp:fig:syn} 中标记为 \ding{173} 的地方),它将此数据包中的数据存储在一个称为 \textit{SYN 队列} 的队列中\index{SYN 队列}。在这一步时,连接还没完全建立,所以被称为半打开连接\index{半开放连接},即只有客户端到服务端方向的连接被确认,而服务端到客户端方向的连接还没初始化。因此,SYN 队列只存储半开放连接的信息。在服务端从客户端得到 ACK 包后,连接将完全建立。TCP 会从 SYN 队列中移除半开放连接,并将其添加到另一个称为 \textit{Accept 队列} 的队列中。当一个进程调用 \texttt{accept()} 时,TCP 将从 Accept 队列中移出一个连接并交给应用程序。图~\ref{tcp:fig:syn} 描述了这个过程。



重传。如果最终的 ACK 包没能到达,服务器会重新发送 SYN+ACK 数据包。如果重新发送了多次后,ACK 包还是不能到达,那么在 SYN 队列中存储的记录最终会因为超时被丢弃。服务器重新发送 SYN+ACK 数据包的次数取决于内核参数。我们可以通过以下命令读取和设置此内核参数:

$ sysctl net.ipv4.tcp_synack_retries
net.ipv4.tcp_synack_retries = 5

// 将值更改为 10
$ sudo sysctl -w net.ipv4.tcp_synack_retries=10

SYN 队列的大小。SYN 队列中可以存储多少个半开放连接取决于队列的大小,这由操作系统根据系统中的内存数量决定。系统内存越多,SYN 队列的大小就会越大。我们可以通过以下内核参数读取和更改队列大小:

$ sysctl net.ipv4.tcp_max_syn_backlog
net.ipv4.tcp_max_syn_backlog = 512

// 将队列大小更改为 128
$ sudo sysctl -w net.ipv4.tcp_max_syn_backlog=128
Last modified: Tuesday, 2 September 2025, 2:55 PM