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
- 客户端:调用connect,把本地的socket状态设置为TCP_SYN_SENT,选择一个可用端口发出SYN握手请求并启动重传定时器。
- 服务端:正常情况下记录请求并返回 SYN+ACK,等待第三次 ACK;队列溢出时的丢弃、SYN cookies 等处理取决于内核和配置。
- 客户端:收到合法 SYN+ACK 后进入 ESTABLISHED 并发送 ACK。TCP keepalive 只有设置 SO_KEEPALIVE 才启用,并非建立连接就自动启用。
- 服务器:响应第三次ACK,把当前的半连接对象删除,创建新的socket加入到全连接队列,最后设置连接状态为ESTABLISHED
假设双向单程延迟近似相等,忽略处理时间与重传时,客户端通常在约 1 RTT 后收到 SYN+ACK,阻塞式 connect 可返回;服务端约在 1.5 RTT 后收到第三次 ACK。这是不同观察点,不能统一说建连耗时为 1.5 RTT。
RTT(Round-Trip Time 往返时延):指的是一个数据包从 A 发到 B,再从 B 返回 A,整个来回所花的时间。