TCP/IP Essentials for Backend Engineers

Three-way handshake,滑动窗口, TIME_WAIT, CLOSE_WAIT, half-open connections and the tools to see them all.

IntermediateUpdated 2026-10-2941K views

Handshake and teardown

TCP is connection-oriented and reliable — 'connection' here just means both sides keep per-connection state (sequence numbers, buffers, timers). Establishing it costs 3 packets, closing it costs 4.

客户端                              服务端
  │  SYN seq=x            →            │   ①三次握手
  │           ←  SYN+ACK seq=y,ack=x+1  │
  │  ACK ack=y+1          →            │   连接建立
  │                                    │
  │  GET / HTTP/1.1        →           │   数据传输
  │           ←  HTTP/1.1 200 OK        │
  │                                    │
  │  FIN seq=u             →           │   ②四次挥手
  │           ←  ACK ack=u+1            │
  │           ←  FIN seq=v              │
  │  ACK ack=v+1           →           │   服务端先进入 TIME_WAIT?
  │                                    │
  CLOSE_WAIT:收到 FIN、还没 close()    │   FIN_WAIT_2
  TIME_WAIT:主动关闭方等 2MSL          │
  • TIME_WAIT stays 2xMSL (~60s) on the side that actively closes, to soak up delayed/duplicate packets of the old connection
  • CLOSE_WAIT is YOUR fault: the peer closed but your code hasn't called close() — find the socket leak
  • Servers should let clients close first, or burn TIME_WAIT slots when they close first
A server under TIME_WAIT pressure: net.ipv4.tcp_tw_reuse=1 lets a NEW connection reuse a TIME_WAIT local port safely (timestamp-protected). Do NOT set tcp_tw_recycle (removed in 4.12) — it breaks clients behind NAT.

Reliability machinery: sequence, window, congestion

Every byte gets a sequence number; the receiver advertises a sliding window of how much buffer it has left; senders additionally respect a congestion window that grows slowly (slow start → congestion avoidance) and collapses on loss.

MechanismProtects against
序列号 + 校验和乱序、损坏
重传定时器 (RTO)丢包(超时重传,退避加倍)
快重传 + 快恢复连续 3 个重复 ACK 就不等超时
滑动窗口 (rwnd)接收方处理不过来
拥塞窗口 (cwnd)网络中间链路拥塞
# 查看连接状态分布(经典排障命令)
ss -ant | awk '{print $1}' | sort | uniq -c | sort -rn

# 查看内核 TCP 参数
sysctl net.ipv4.tcp_tw_reuse net.ipv4.ip_local_port_range \
       net.ipv4.tcp_max_tw_buckets net.core.somaxconn
For a reverse proxy the classic exhaustion story: 60k outbound ephemeral ports × 60s TIME_WAIT ≈ 1000 new connections/sec per (src_ip, dst_ip, dst_port) tuple. The fix is longer-lived upstream connections (keepalive pool), not sysctl tricks.

Application-level habits that matter

  1. Set SO_KEEPALIVE (or app-layer heartbeats): a silently dead peer can leave a half-open socket forever otherwise
  2. Set reasonable read timeouts: a slow-loris client can pin your worker threads
  3. Graceful shutdown = stop accepting, drain in-flight, then close: this keeps you out of the 'connection reset' race
# Go:同时设心跳、读超时与优雅停机
ln, _ := net.Listen("tcp", ":8080")
srv := &http.Server{
    ReadHeaderTimeout: 10 * time.Second,
    ReadTimeout:       30 * time.Second,
    IdleTimeout:       120 * time.Second,
}
go srv.Serve(ln)
<-stop                                     // 收到 SIGTERM
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(ctx)                          // 排空在途连接再退

Friends & Partners