TCP建立连接的过程

listen

listen 将 socket 标记为被动监听状态。理解正常握手时,可以区分未完成握手的请求和已完成握手、等待 accept 的连接;开启 SYN cookies 等机制时,不一定为每个 SYN 保存半连接记录。

全连接队列

Linux 中,backlog 控制已完成握手、等待 accept 的连接队列上限;超过 net.core.somaxconn 的请求值会被截到该上限。具体边界行为和内部结构以内核版本为准。

半连接队列

未完成握手的请求受 tcp_max_syn_backlog、内核实现和 SYN cookies 等因素影响。旧版本源码中的哈希表大小计算不能作为所有 Linux 版本通用的队列容量公式。

connect

​ 客户端在执行connect函数的时候,会把本地socket状态设置为TCP_SYN_SENT,选择一个可用的端口,接着发出SYN握手请求并启动重传定时器。(一个客户端最大能建立的连接数不是65535,只要服务器足够多,单机可能建立百万级连接,实际还受文件描述符、内存、端口分配方式等限制)

连接建立过程总结

服务端

服务端的核心就是创建socket绑定端口、listen监听、最后accept客户端的请求

int main(){
    int fd =socket(AF_INET,SOCK_STREAM,0);
    bind(fd,..);
    listen(fd,128);
    accept(fd,..);
}

客户端

主要是创建socket,调用connect连接服务器

int main(){
    int fd =socket(AF_INET,SOCK_STREAM,0);
    connect(fd,..);
    ...
}
sequenceDiagram
    autonumber

    participant C as 客户端
    participant S as 服务端 TCP/IP 协议栈
    participant Q1 as 半连接队列
    participant Q2 as 全连接队列
    participant A as 服务端应用程序

    Note over S,A: listen()<br/>申请并初始化接收队列

    C->>C: 选择可用的本地端口
    C->>S: SYN
    Note left of C: connect()<br/>1. 发送 SYN 握手请求<br/>2. 启动重传定时器

    S->>Q1: 创建半连接记录
    S-->>C: SYN + ACK
    Note right of S: 1. 发送 SYN+ACK<br/>2. 进入半连接队列<br/>3. 启动重传定时器

    C->>C: 清除 SYN 重传定时器
    C->>C: 设置连接状态为已连接
    C->>S: ACK

    S->>S: 创建新的已连接 socket
    S->>Q1: 从半连接队列移除
    S->>Q2: 加入全连接队列

    A->>S: accept()
    S->>Q2: 从全连接队列取出 socket
    S-->>A: 返回已连接 socket
  1. 客户端:调用connect,把本地的socket状态设置为TCP_SYN_SENT,选择一个可用端口发出SYN握手请求并启动重传定时器。
  2. 服务端:正常情况下记录请求并返回 SYN+ACK,等待第三次 ACK;队列溢出时的丢弃、SYN cookies 等处理取决于内核和配置。
  3. 客户端:收到合法 SYN+ACK 后进入 ESTABLISHED 并发送 ACK。TCP keepalive 只有设置 SO_KEEPALIVE 才启用,并非建立连接就自动启用。
  4. 服务器:响应第三次ACK,把当前的半连接对象删除,创建新的socket加入到全连接队列,最后设置连接状态为ESTABLISHED

假设双向单程延迟近似相等,忽略处理时间与重传时,客户端通常在约 1 RTT 后收到 SYN+ACK,阻塞式 connect 可返回;服务端约在 1.5 RTT 后收到第三次 ACK。这是不同观察点,不能统一说建连耗时为 1.5 RTT。

RTT(Round-Trip Time 往返时延):指的是一个数据包从 A 发到 B,再从 B 返回 A,整个来回所花的时间。

参考:listen(2);tcp(7)。