我的语音合成服务连续几天启动失败。日志里只有一句 bind: EACCES,但端口明明没人用。这个坑我查了很久,最后发现是 Windows 的一个隐藏机制:保留端口段。

现象

服务是一个本地 TTS 服务器,要绑 8721 端口。每次启动都失败,报 EACCES(权限被拒绝)。

奇怪的是:

  • netstat 显示 8721 没有任何进程占用
  • 换到 9221 立刻就能绑上
  • 用管理员权限运行也一样失败

根因

执行 netsh interface ipv4 show excludedportrange protocol=tcp,你会看到一堆「排除端口段」:

8617 - 8716
8717 - 8816
8817 - 8916
9015 - 9114

8721 正好落在 8717–8816 这个区间里。

这些端口被 Windows 系统服务(通常是 Hyper-V / WSL / Docker 相关的端口预留机制)保留了。普通进程去绑,系统直接返回 EACCES,而不是 EADDRINUSE。所以你看端口是「空」的,却绑不上。

实测验证:

端口结果
8721 / 8722 / 8723EACCES
8716 / 8816 / 8817 / 9114EACCES
9221 / 9222 / 7321 / 18721✅ OK

连锁反应

更糟的是,我配了一个「看门狗」计划任务,每 60 秒检查一次 8721 端口通不通,不通就拉起服务。

于是形成了一个死循环:

  1. 检查 8721 → 不通
  2. 启动 TTS 服务 → 加载 3.5 GB 模型 → 占 4.1–4.5 GB 内存
  3. GPU 预热约 40 秒
  4. 绑端口 → 失败 → 进程退出
  5. 回到第 1 步

日志里一天出现了 244 次失败启动。内存被反复吃掉又释放,机器卡得不行。

修复

把配置里的端口从 8721 改成 9221,三处:

  • endpoint
  • speech.localProcess.endpoint
  • 看门狗脚本的检查端口

改完,服务常驻,看门狗不再空转,卡顿和黑框一起消失。

教训

  • EACCES 不等于「权限不够」。 在 Windows 上,它也可能是「端口被系统保留」。
  • 遇到端口绑定失败,先查保留段:netsh interface ipv4 show excludedportrange protocol=tcp。
  • 看门狗要有「启动失败次数上限」。 否则一次配置错误就能变成 244 次无效启动。
  • 换端口时,把所有相关配置一起换。 漏一个就还是连不上。

这个坑不深,但很隐蔽。写出来,希望对同样用 Windows 跑本地服务的人有用。