1. 端口复用基础:从一次线上故障说起

先讲个我自己的经历。几年前我负责一个网关服务,压测到 8 万并发时,新连接突然全部失败。查了半天,发现是端口不够用了——每个连接都独占一个本地端口,系统默认的 28232 个端口很快被耗尽。

当时我就在想:能不能让多个连接共享同一个端口?这就是端口复用的起源。说白了,端口复用就是让多个 socket 绑定到同一个 IP 和端口上,而不是每个 socket 独占一个端口。

1.1 什么是端口复用

端口复用,英文叫 Port Reuse。它允许不同的 socket 绑定到相同的 IP 地址和端口号。正常情况下,一个端口只能被一个 socket 占用。但有了端口复用,多个 socket 可以共享同一个端口。

举个例子:你的 Nginx 监听 80 端口,同时你的测试程序也想绑定 80 端口。没有端口复用,第二个程序会报错 "Address already in use"。开启端口复用后,两个程序都能绑定成功。

核心概念:端口复用不是让多个连接共用同一个端口,而是让多个 socket 可以绑定到同一个端口。每个连接仍然有独立的四元组(源 IP、源端口、目标 IP、目标端口)。

1.2 为什么需要端口复用

我总结了一下,端口复用主要解决三个场景的问题:

  • 高并发连接:单机需要支撑百万级连接时,端口不够用。复用端口可以突破 65535 的限制。
  • 快速重启服务:服务崩溃后立即重启,如果旧连接还在 TIME_WAIT 状态,新进程就无法绑定端口。端口复用允许新进程立即接管。
  • 多实例部署:同一台机器上跑多个服务实例,每个实例都想监听同一个端口。端口复用让这成为可能。

你想想看,一个 C10M(千万并发)场景下,如果每个连接都独占一个端口,那得需要多少个端口?端口复用就是解决这个问题的关键。

1.3 SO_REUSEADDR 与 SO_REUSEPORT 的区别

这两个选项经常被搞混。我刚开始学的时候也分不清,直到在一次线上事故中踩了坑才彻底搞明白。

特性 SO_REUSEADDR SO_REUSEPORT
主要用途 允许重用 TIME_WAIT 状态的端口 允许多个进程绑定同一端口
绑定规则 后绑定的进程必须指定更具体的 IP 所有进程绑定条件完全相同
负载均衡 无,由内核随机分配 内核提供,近似均匀分配
安全性 低,恶意程序可劫持端口 高,只允许同一用户下的进程
Linux 版本 所有版本支持 3.9+ 内核支持

SO_REUSEADDR 详解

这个选项解决的是 TIME_WAIT 问题。当一个 TCP 连接关闭后,端口会进入 TIME_WAIT 状态,默认持续 2MSL(约 60 秒)。这段时间内,其他程序无法绑定这个端口。

设置 SO_REUSEADDR 后,即使端口处于 TIME_WAIT 状态,新 socket 也能绑定。这在服务重启时特别有用。

// C 语言示例:设置 SO_REUSEADDR
int opt = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
    perror("setsockopt SO_REUSEADDR failed");
    exit(EXIT_FAILURE);
}

避坑指南:我曾经在开发环境遇到过一个问题——设置了 SO_REUSEADDR 后,两个不同端口的服务互相干扰。后来发现是因为绑定了通配地址 0.0.0.0,导致端口冲突。记住:SO_REUSEADDR 只解决 TIME_WAIT 问题,不解决端口冲突问题。

SO_REUSEPORT 详解

这个选项是 Linux 3.9 内核引入的。它允许多个进程或线程绑定到完全相同的 IP 和端口。内核会在这些 socket 之间做负载均衡,将新连接均匀分发。

// C 语言示例:设置 SO_REUSEPORT
int opt = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) < 0) {
    perror("setsockopt SO_REUSEPORT failed");
    exit(EXIT_FAILURE);
}

SO_REUSEPORT 的好处很明显:

  • 多进程监听同一端口,天然支持多核扩展
  • 内核做负载均衡,减少锁竞争
  • 进程独立,一个进程崩溃不影响其他进程

注意:SO_REUSEPORT 不是万能的。如果连接处理时间差异很大,内核的简单哈希分发可能导致负载不均。我曾在压测中发现,某些 worker 进程的连接数比其他进程多 30%。解决方案是使用 SO_REUSEPORT 的 BPF 分流功能,或者在上层加一层负载均衡。

1.4 端口复用的知识体系

下面这张图展示了端口复用的核心知识结构,我把它画出来方便你理解:

端口复用知识体系 端口复用 什么是端口复用 为什么需要端口复用 SO_REUSEADDR vs SO_REUSEPORT 共享端口 突破 65535 限制 高并发连接 快速重启 TIME_WAIT 重用 多进程负载均衡 核心要点 SO_REUSEADDR:解决 TIME_WAIT 问题,适合服务重启 SO_REUSEPORT:多进程共享端口,适合高并发场景

1.5 实际应用场景

我整理了几个常见的应用场景,你可以对照自己的项目看看:

  • Nginx 多 worker 进程:每个 worker 进程都监听 80 端口,使用 SO_REUSEPORT 让内核做负载均衡。这是最经典的应用。
  • Redis 哨兵模式:哨兵进程需要快速接管主节点端口,使用 SO_REUSEADDR 避免 TIME_WAIT 阻塞。
  • 自定义代理服务器:代理服务器需要大量本地端口连接后端,使用端口复用可以大幅提升连接数。

我的建议:如果你的服务需要频繁重启,优先使用 SO_REUSEADDR。如果是多进程高并发场景,SO_REUSEPORT 是更好的选择。但要注意,SO_REUSEPORT 在 Linux 3.9 以下内核不可用,生产环境需要确认内核版本。