# 两个程序想听同一个端口，Linux 上 SO\_REUSEADDR 和 SO\_REUSEPORT 别混着用

**URL:** <https://www.shiqidu.com/t/topic/1923>\
**Category:** 开发调优\
**Tags:** Linux, HTTP, 编程, 服务器\
**Created:** [2026 年11 月 4 日 03:52 UTC](https://www.shiqidu.com/t/topic/1923 "2026-11-04T03:52:29Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![shiqidu\_editor](https://www.shiqidu.com/user_avatar/www.shiqidu.com/shiqidu_editor/32/528_2.png) [@shiqidu\_editor](https://www.shiqidu.com/u/shiqidu_editor)\
发布日期： [2026 年11 月 4 日 03:52 UTC](https://www.shiqidu.com/t/topic/1923/1 "2026-11-04T03:52:29Z")

</div>

第二个 worker 一起 bind，终端弹出 `Address already in use`。有人随手加上 `SO_REUSEADDR`，以为「复用」这个词就够了。有人听过 `SO_REUSEPORT`，分不清该在哪一层设、设几次。Linux 手册 [socket(7)](https://man7.org/linux/man-pages/man7/socket.7.html) 把两个选项写在一起，对照着看就不容易混。下面按手册口径拆开讲，不讨论怎么去抢别人已经在听的端口，也不写野路子劫持。

## SO\_REUSEADDR 放宽的是什么

`SO_REUSEADDR` 表示：校验 `bind(2)` 地址时，规则放宽一点，允许复用本地地址。对 `AF_INET` 来说，通常仍不能在已经有活跃 listen socket 占着同一地址时再抢同一端口。当监听方绑在 `INADDR_ANY` 加某个端口上时，其它本地地址想再绑这个端口一般也不行。它解决的更多是 TIME\_WAIT 一类场景下的「地址暂时被认为还占用着」，方便服务重启后尽快再 bind，不是「多个独立 listener 和平共处」的开关。

手册 NOTES 里还提了一点和别的系统不一样的地方：在 Linux 上，想复用端口时，前一个绑过这个端口的程序和后来想复用的程序，通常都需要设过 `SO_REUSEADDR`。只在新进程上打开，有时仍绑不上。日常写服务时两边都设，能少踩一点跨平台直觉差。重启后偶发 `EADDRINUSE`、过一会儿又好了，优先怀疑的是旧连接还在 TIME\_WAIT，而不是「内核不允许你再听」。

## SO\_REUSEPORT 才是多 listener

`SO_REUSEPORT`（Linux 3.9 起）才允许多个 `AF_INET` / `AF_INET6` socket 绑到同一个地址。手册写得很死：每个 socket 都要在调用 `bind(2)` 之前设好这个选项，包括第一个。为防端口被别的用户抢走，绑同一地址的进程需要相同的有效用户 ID。TCP 和 UDP 都能用。

TCP 上，每个线程各自一个 listener，再 `accept`，往往比「一个线程 accept 再分发」或「多线程抢同一个 listen fd」更均匀。UDP 上，多进程 / 多线程各自收包，也比大家抢同一个 socket 好分一些。需要自定义把包分给 reuseport 组里哪一个 socket 时，还有 `SO_ATTACH_REUSEPORT_CBPF` / `SO_ATTACH_REUSEPORT_EBPF`。UDP 从较新的内核起支持，TCP 也随后跟上。BPF 程序按加入组的顺序给 socket 编号，返回非法下标会退回普通 REUSEPORT 分配。那是进阶用法，日常先把「每个 fd 都在 bind 前设 REUSEPORT」做对就够。

## 实操时别踩的点

- 只开 `SO_REUSEADDR`、指望两个服务同时听同一端口，按手册描述通常行不通。报错看起来像「端口被占」，根因却是选项选错。
- 多进程 / 多线程 listener：每个 socket 都要设 `SO_REUSEPORT`，漏掉一个就会在 bind 阶段炸。第一个 listener 也不能省。
- 不要指望靠这些选项去「接管别人已经在听的端口」。同 euid 限制本身就是防劫持的设计。
- 排错时顺手确认一下是不是旧进程还占着端口、是不是绑到了 `0.0.0.0` 和具体网卡地址冲突，别把所有 `EADDRINUSE` 都怪到缺 REUSEPORT 上。
- HTTP 反向代理、多 worker 模型里，如果框架文档要求显式打开 reuseport，就按框架的 socket 创建路径设，别只在业务层「以为内核会自动帮你」。

写代码时建议把 `setsockopt` 紧挨在 `socket()` 之后、`bind()` 之前，并检查返回值。有的封装库把 option 暴露成构造参数或 `listen` 辅助函数。若文档写明「内部已设 REUSEPORT」，仍应用 `getsockopt` 或启动日志确认一次，避免以为设了其实没设。容器网络、宿主端口映射再叠一层时，宿主侧看到的占用和容器内 bind 失败也可能不是同一回事，排错时把 `ss -lntp` / `netstat` 的输出和进程树对上再改选项。

你们线上是单进程 listen 多线程 accept，还是一开始就按 REUSEPORT 拆多个 listener？设漏一个 fd 的坑踩过没有？
