同一个端口能被两个程序监听吗?TCP 11 组绑定实测

先给三句话的答案。

默认不行:第二个程序去 bind 同一个地址端口,会拿到 WinError 10048「每个套接字地址(协议/网络地址/端口)只允许使用一次」。

但只要两边都把 SO_REUSEADDR 打开,不行就变成了行。

两个独立进程都 bind 成功、都 listen 成功,netstat -ano 里并排两行 LISTENING,挂着两个不同的 PID(实测 9916 和 31928)。

代价藏在第三句:后绑的那个程序是个哑巴。

我发了 10 条连接,10 条全被先绑的接走,后绑的 accept 次数是 0。

所以「端口被占用」不一定是残留进程在捣鬼。

更可能是某个程序或它依赖的库打开了 SO_REUSEADDR,把端口借了出去。

为什么值得看:这个报错的锅,一半不在残留进程

SO_REUSEADDR 这个名字太容易被误读成「允许复用端口」。

于是很多人把它当万能开关:服务起不来就顺手加一行。

它的本意是让服务在重启时不被 TIME_WAIT 卡住,不是为了两个进程共享端口。

Windows 在这条路上比 BSD 和 Linux 走得更远。

它允许两个打开了 SO_REUSEADDR 的套接字绑同一个地址端口,而且两个 listen 都会返回成功。

系统一声不吭,问题被推迟到连接进来的那一刻。

这次想钉死四个问题:什么情况下第二个 bind 会失败、失败给的是哪个错误码、两边都开复用之后连接到底归谁、想要真·多进程共用端口在 Windows 上有没有解。

怎么测的:11 组绑定,逐组记录返回码和连接归属

环境是 Windows 10.0.26200、CPython 3.11.15,全部走本机回环。

端口取 47811~47851 这类高位端口,避免撞上正在跑的服务。

每一组都真跑:先 bind 再 listen,异常按原样打印 winerror 和错误文字。

凡是两边都绑成功的,再补一次真实连接,用 accept 判断连接落在哪一把套接字上。

为了区分「一时运气」和「稳定行为」,统计组每组连 10 次。

矩阵是同一进程内两把套接字,地址 127.0.0.1:

组 场景 第一个 bind/listen 第二个 bind/listen 连接归谁
1 两边都不设 SO_REUSEADDR OK FAIL 10048 ---
2 两边都设 SO_REUSEADDR=1 OK 都 OK 先绑的(10/10)
3 只有先绑的设 SO_REUSEADDR OK FAIL 10048 ---
4 只有后绑的设 SO_REUSEADDR OK FAIL 10013 ---
5 先 0.0.0.0、后 127.0.0.1,都不设复用 OK 都 OK 127.0.0.1 那把(10/10)
6 先 127.0.0.1、后 0.0.0.0,都不设复用 OK 都 OK 127.0.0.1 那把(10/10)
7 先 :: 双栈(IPV6_V6ONLY=0)、后 0.0.0.0 OK 都 OK 走 IPv4 的那把
8 UDP 两边都不设复用 OK FAIL 10048 ---
9 UDP 两边都设复用 OK 都 OK 先绑的(后绑的收不到数据)
10 端口处于 TIME_WAIT 时重绑 不设复用 OK / 设复用 OK --- ---
11 独占旗标与复用旗标互抢 见下表 10048 / 10013 ---

拿两个真正的独立进程复跑,结论一致。

两个都不设复用:第二个进程 10048。

两个都设复用:两个进程都进 LISTENING,10 条连接全给了先绑的那个。

复现方式(同一台机器上,脚本和原始输出都在下面的证据目录里):

bash 复制代码
python evidence/bind_matrix.py     # 11 组矩阵,含错误码原文
python evidence/bind_matrix2.py    # 逐个追「谁收到了连接」
python evidence/bind_matrix3.py    # 每组 10 次连接的统计
python evidence/two_proc.py        # 跨进程版,并打印 netstat 的 PID

原始输出在 evidence/ 下:bind-matrix-win.txt、bind-matrix-win-2.txt、bind-matrix-win-3.txt、bind-two-process-win.txt。

验证与踩坑:四个和直觉相反的结果

第一个坑是错误码会把你带偏。

只有后绑的那个打开 SO_REUSEADDR 时,失败码不是熟悉的 10048。

它给的是 WinError 10013「以一种访问权限不允许的方式做了一个访问套接字的尝试」。

同一类问题两种码,网上搜到的答案经常答不到点上。

第二个坑是「先绑的赢」这件事很稳。

两边都开复用时,我连了 10 次,10 次都被先绑的接走。

共享状态下不存在负载均衡,后绑的那个只是占着位置等饿死。

它的健康检查、启动日志、端口占用检查全部正常,业务却一条请求都收不到。

第三个坑是通配地址与具体地址抢端口时,赢的是地址更具体的那把,和谁先绑无关。

先绑 0.0.0.0:47841、后绑 127.0.0.1:47841 都不报错,10 条来自 127.0.0.1 的连接全部落到后者。

IPv6 双栈同理:先绑 :: 再绑 0.0.0.0 都能成功,而 127.0.0.1 上的请求被 IPv4 那把接走。

这类端口被悄悄分流的故障,日志里看不出任何异常。

第四个坑是 TIME_WAIT 背了太久的锅。

服务端侧先关闭后,netstat 里确实留着一排该端口的 TIME_WAIT,还有一条 FIN_WAIT_2。

但在这种状态下重绑同一个端口,设不设 SO_REUSEADDR 都直接成功。

重启失败先别怪 TIME_WAIT,先看有没有另一个进程正趴在那个端口上。

UDP 比 TCP 更狠一点。

两边都开复用后,两个套接字都能绑定成功。

但一条数据发给 127.0.0.1:47812,只有先绑的收到,后绑的那把一个字节都没拿到。

套接字存在、绑定成功、却永远收不到数据,比直接报错难查得多。

结果与经验:什么时候该用哪个旗标

先把结论落成一张表,按你要解决的问题选:

你要解决的问题 正确做法 实测依据
服务重启时报 10048 / 10013 先查是不是真有另一个进程在监听 第 1、3、4 组
不想让别的进程分享自己的端口 显式给监听套接字设 SO_EXCLUSIVEADDRUSE 第 11 组:独占先占住,带复用的一方来抢会拿到 10013;反过来独占的一方后进来抢已被复用的口,也抢不到(10048)
两个进程真想一起分担同一个端口 Windows 上没有这个能力,改用单进程内多线程或异步 IO 本机 socket.SO_REUSEPORT 不存在(hasattr 为 False)
想让连接均匀分摊到多进程 只有 Linux 3.9+ 的 SO_REUSEPORT 能做,分摊由内核决定 本机没有 Linux 环境,这一条是文档语义,未实测

第一条经验:排查端口占用时别只看有没有残留进程。

如果 netstat -ano 里同一个端口出现两行 LISTENING、PID 却不一样,说明已经有两个程序在共享它。

此时杀哪个都不一定对,因为连接实际归先绑的那个。

先看这两行出现的时间顺序,再决定动谁。

第二条经验:自己写服务时,Windows 上不要顺手给监听套接字加 SO_REUSEADDR。

它的语义在这里等于「欢迎别人来分享我的端口」,还不会给你任何回报。

共享状态下你先绑的那把能拿到全部连接,看起来一切正常。

直到哪天你的进程成了后绑的那个,才会以「服务在跑但没流量」的形式爆出来。

想保护自己就用 SO_EXCLUSIVEADDRUSE。

下一步打算把这套矩阵搬到容器里再跑一遍,用 Linux 上的 SO_REUSEPORT 做同样的 A/B 对照。

两个进程共用一个端口到底怎么分摊,这部分在这台 Windows 上测不了,就不硬写了。

相关推荐
deli00717 分钟前
红绿灯配时实测:固定 60 秒 vs 自适应,2400 辆车差多少
前端
去伪存真18 分钟前
井道无信号、满手黑机油,我们如何用 DeepSeek V4 打造不“犯浑”的电梯维保 Agent?
前端·agent
deli00720 分钟前
60fps 为什么还是一顿一顿?帧时间曲线实测给你看
前端
时间的情敌1 小时前
Cesium 流动管线、管道动画、管线流向仿真,管网、油气、水利孪生高频需求
前端·交互·cesium
wangxiaoxuan2172 小时前
SLAM 的基本结构:前端、后端、回环与地图优化
前端·机器人·科沃斯八界
Agent世界5 小时前
用 CDP 做发布自动化:四道机器闸门与产物验收
前端·javascript
FungLeo5 小时前
成为全栈·Vue 管理后台篇·编辑器图片上传:从文件选择到文章附件关联
前端·vue.js·编辑器·markdown·文件上传·formdata·附件管理
某哈压力大6 小时前
SSH 突然 Connection reset?不是密钥坏了,是 OpenSSL 把 sshd 干趴了
前端·node.js·命令行
程序员大阳6 小时前
副队长ES6+教程(3)--解构赋值
前端·javascript·es6·赋值·解构