bigywater.tech
网络协议 · 59

SO_REUSEADDR 与 SO_REUSEPORT:端口复用的两种含义

“Address already in use”几乎人人遇到过,标准解法是 SO_REUSEADDR;后来又有了 SO_REUSEPORT。这两个选项到底分别允许什么、不允许什么?这篇在 macOS 上把 bind 冲突的组合一个个跑出来,还看了 REUSEPORT 到底会不会分摊连接。

0 / 0 / 0 / 400
REUSEPORT 下 4 个监听者各 accept 到的连接数:全部给最后绑定的
EADDRINUSE
两边都只设 REUSEADDR 时第二个监听者 bind 同一个地址端口的结果
100 / 0
通配 0.0.0.0 与具体 127.0.0.1 同端口时,连 127.0.0.1 的 100 次被谁接走

谁和谁能共存

实测 · 本机执行输出
  两边都设 都不设                 :第二个 bind 同一个 127.0.0.1:P → EADDRINUSE
  两边都设 REUSEADDR           :第二个 bind 同一个 127.0.0.1:P → EADDRINUSE
  两边都设 REUSEPORT           :第二个 bind 同一个 127.0.0.1:P → 成功
  两边都设 REUSEADDR+REUSEPORT :第二个 bind 同一个 127.0.0.1:P → 成功
  只有一边设:
  第一个设 REUSEPORT、第二个不设 → EADDRINUSE;第一个不设、第二个设 REUSEPORT → EADDRINUSE
  第一个设 REUSEADDR、第二个不设 → EADDRINUSE;第一个不设、第二个设 REUSEADDR → EADDRINUSE

两个监听者共用同一个地址端口,要两边都设 REUSEPORT;只设 REUSEADDR 或只有一边设都是 EADDRINUSE。

TIME_WAIT

实测 · 本机执行输出
  重新 bind 时设 都不设         :netstat 里这个端口的状态 ['TIME_WAIT'];新套接字 bind + listen → EADDRINUSE
  重新 bind 时设 REUSEADDR   :netstat 里这个端口的状态 ['TIME_WAIT'];新套接字 bind + listen → 成功
  重新 bind 时设 REUSEPORT   :netstat 里这个端口的状态 ['TIME_WAIT'];新套接字 bind + listen → 成功

端口上留有 TIME_WAIT 时,重新 bind 需要 REUSEADDR(这台 macOS 上 REUSEPORT 也行)。

通配与具体

实测 · 本机执行输出
  先 0.0.0.0 后 127.0.0.1,都不设               → EADDRINUSE
  先 0.0.0.0 后 127.0.0.1,都设 REUSEADDR      → 成功
  先 127.0.0.1 后 0.0.0.0,都设 REUSEADDR      → 成功
  先 0.0.0.0 后 127.0.0.1,都设 REUSEPORT      → 成功
  两个都设 REUSEADDR 时,连 127.0.0.1:P 100 次,被谁接走:{'通配 0.0.0.0': 0, '具体 127.0.0.1': 100}(具体地址优先于通配)

绑 0.0.0.0 和绑具体 IP 的两个套接字,设了 REUSEADDR 就能共存,连接给更具体的那个。

没有负载均衡

实测 · 本机执行输出
  4 个监听者各 accept 到:[0, 0, 0, 400](合计 400)

这台 macOS 上 REUSEPORT 的 4 个监听者,400 个连接全部给了最后绑定的一个。

UDP 单播

实测 · 本机执行输出
  3 个 UDP 套接字各收到:[0, 0, 300](合计 300/300)
  换成 30 个不同源端口的发送方、各发 10 个(共 300):各收到 [0, 0, 300](合计 300/300)

3 个 UDP 套接字同绑一个端口,无论 1 个还是 30 个发送方,300 个数据报都全部给最后绑定的那个。

多播

实测 · 本机执行输出
  只 REUSEADDR,绑 0.0.0.0           :3 次 bind → ['成功', 'EADDRINUSE', 'EADDRINUSE'];已绑定的套接字各收到 [10](共发 10 个)
  REUSEADDR+REUSEPORT,绑 0.0.0.0   :3 次 bind → ['成功', '成功', '成功'];已绑定的套接字各收到 [10, 10, 10](共发 10 个)
  只 REUSEADDR,绑多播组地址              :3 次 bind → ['成功', '成功', '成功'];已绑定的套接字各收到 [10, 10, 10](共发 10 个)

多播要多个套接字都收到,需要两个选项都设,或把套接字绑到多播组地址。

分步演示

REUSEADDR 与 REUSEPORT
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。全部在本机 lo0 上,单个进程里的多个套接字;负载均衡只看了 400 个连接 / 300 个数据报,没有多进程的情形
    • 没有验证:Linux 上 REUSEPORT 的哈希分流;多个进程(而不是同一进程)用 REUSEPORT 的分流;不同用户之间 REUSEPORT 的安全限制;IPv6 的 IPV6_V6ONLY 对 bind 冲突的影响;SO_REUSEPORT_LB(FreeBSD 的分流选项);REUSEPORT 下关闭其中一个监听者时已排队连接的去向
    京ICP备2026067502号-1京公网安备11010502063487号