socket激活本质是“端口先行,服务后到”,即systemd先bind/listen端口,连接到来时才启动服务并传递已就绪socket fd;服务需配置SocketActivate=yes、Type=exec,且不能自行指定端口。

socket激活本质是“端口先行,服务后到”
Systemd 的 socket 单元不是让服务去监听端口,而是让 systemd 自己先 bind() 和 listen(),等第一个连接进来,再拉起服务进程,并把已就绪的 socket 文件描述符直接传过去。这和传统方式(服务自己 bind → listen → accept)完全相反——所以你不能把 socket-activated-http.service 当成普通 service 去 systemctl start,它必须由对应 socket 触发。
- 服务单元里必须写
SocketActivate=yes,否则 systemd 不会传递 socket fd -
Type=exec是安全选择;Type=simple会失败,因为 simple 类型假设进程一启动就“活了”,但 socket 激活要求进程从 systemd 接收 fd 后才真正开始工作 - 服务命令里不能带端口号参数(比如
python3 -m http.server 8080),否则会冲突——端口实际由 socket 单元绑定,服务只负责从 fd 读写
必须配对:.socket 文件 + .service 文件 + Sockets= 关联
缺一不可。只有 .socket 文件,端口被占着但没人处理连接;只有 .service 文件,没 socket 触发,服务永远不会启动;关联错名字,fd 就传不到正确位置。
-
.socket文件名(如http-8080.socket)和.service文件名(如http-8080.service)建议保持前缀一致,便于管理 -
[Socket]段中必须有ListenStream=8080(或ListenStream=/run/myapp.sock),且协议要匹配服务预期(TCP/UDP/Unix) -
[Service]段中必须有Sockets=http-8080.socket(值为 socket 文件名,不含扩展名),这是唯一告诉 systemd “把这个 socket fd 交给我的方式” - 别漏掉
[Unit] Requires=http-8080.socket,否则systemctl start http-8080.service可能因 socket 未就绪而报Job for ... failed
常见错误:端口被占、fd 未接收、服务秒退
最典型的三个现象:启动后 ss -tlnp | grep 8080 看不到监听(socket 没生效);curl 一次就断,journal 日志里出现 Failed to receive passed file descriptors;服务启动后立刻 exit status=0。
- 端口被占:检查是否已有其他进程(如 nginx、另一个 python server)绑定了同一端口,
sudo ss -tulpn | grep ':8080'直接定位 - fd 未接收:服务代码没调用
sd_listen_fds(3)(C)或没使用systemd-python的listen_fds()(Python),或 Python 脚本没加if __name__ == '__main__': ...守卫导致模块导入时就执行了 bind - 服务秒退:确认
Type=exec,且ExecStart=命令不带后台化(如 nohup/&)、不 fork 子进程;systemd 要求 exec 类型进程就是主服务本身
调试关键命令和日志入口
别靠猜,用这几条命令快速定位卡在哪一环:
- 检查 socket 是否加载并监听:
systemctl status http-8080.socket,看Active:是listening还是failed - 确认 socket fd 是否真传进来了:
journalctl -u http-8080.service -o short-monotonic -n 50,搜索Listening on或Received字样 - 手动触发一次连接来测试流程:
curl -v http://localhost:8080/,然后立刻查systemctl status http-8080.service看是否从inactive变成running - 验证文件描述符传递是否成功:在服务脚本开头加一行
echo "fds: $(ls /proc/$$/fd/)",正常应看到 fd 3(systemd 传入的 socket fd 编号固定为 3+)
最容易被忽略的是:socket 激活下,服务进程生命周期完全由连接驱动——没连接时它根本不存在;一旦所有连接关闭,systemd 默认会停掉服务(除非配置 Accept=false 且服务自己维持长连接)。这不是 bug,是设计使然。










