Linux systemd socket activation 的监听交接
来源:17golang原创
时间:2026-10-17 08:21:16 442浏览 收藏
systemd socket activation 的关键不是替程序多启动一个进程,而是让 .socket 单元持有监听套接字,再把对应文件描述符交给服务。使用 Accept=no 时,程序接收监听 fd,并自行 accept;它不应再次对同一地址 bind。使用 Accept=yes 时交付的是连接套接字,程序模型随之改变。
systemd 官方地址:https://systemd.io/。以下使用用户级单元、IPv4 回环地址和一个固定文本响应程序,只解释 .socket、.service 与 fd 继承,不实现公网服务或 HTTP 服务器。
先约定这个小项目做什么
目标是让本机客户端连接 127.0.0.1:18081 后收到一行 socket-note ready。端口由 socket-note.socket 管理,业务程序 socket-note-worker 只负责接受连接并回复。示例要求 Linux 上存在可用的 systemd 用户管理器、C 编译器、pkg-config 和 libsystemd 开发文件;开发包名称随发行版而异,应从发行版官方仓库安装。
用户级单元保存在 ~/.config/systemd/user/,程序放在 ~/.local/libexec/。这些是读者操作位置,不是已经部署好的产物。本文没有执行构建或启停命令,后文状态均是读者应观察的判断条件,不是实测记录。
由 .socket 描述地址,由 .service 描述程序
将下面内容保存为 ~/.config/systemd/user/socket-note.socket。ListenStream 负责地址,Accept=no 明确要求服务处理监听套接字。同名的 socket-note.service 是默认匹配对象,因此这里无需另写 Service=。
[Unit] Description=Local socket note listener [Socket] # 仅监听本机 IPv4,先确认这个示例端口没有被其他服务使用 ListenStream=127.0.0.1:18081 # 交付监听套接字,由业务程序接受连接 Accept=no [Install] WantedBy=sockets.target
再保存 ~/.config/systemd/user/socket-note.service:
[Unit] Description=Local socket note worker [Service] # 程序保持前台运行,不派生后台守护进程 Type=simple # %h 由 systemd 展开为当前用户的主目录 ExecStart=%h/.local/libexec/socket-note-worker
这个 service 不设置开机目标,也不需要单独 enable。启用的是 socket;连接到来时由它激活服务。Type=simple 不是业务就绪通知协议,不应把服务已被启动当作业务一定能响应。单元名称与 Accept 的关系见 systemd.socket 官方手册源文件,服务类型见 systemd.service 手册。

业务代码接管 fd,而不是重新创建监听端口
将以下完整代码保存为 socket-note-worker.c。sd_listen_fds(1) 读取激活协议并清理相关环境变量,返回收到的 fd 数量;fd 从 SD_LISTEN_FDS_START,即 3 开始。这里只允许一个 IPv4 流式监听套接字,数量或类型不符就退出,不静默退回自行监听。
#include#include #include #include #include #include #include int main(void) { // 清理激活环境;这个示例只接受一个继承的监听 fd int count = sd_listen_fds(1); if (count != 1) { fprintf(stderr, "expected one listener, got %d\n", count); return 1; } int listener = SD_LISTEN_FDS_START; // 最后一项为 0:不限制具体端口,但限定 IPv4、流式和监听状态 if (sd_is_socket_inet(listener, AF_INET, SOCK_STREAM, 1, 0)
程序没有调用 socket、bind 或 listen,因为监听对象已经由管理器准备好。接收方持有自己的 fd 引用,管理器仍可保留同一套接字的引用;“交接”不是把端口从管理器彻底搬走。对监听套接字做 shutdown 可能影响管理器持有的同一对象,因此不能用它代替关闭当前进程自己的 fd。
这是串行、短响应的教学代码,没有并发模型、完整写入超时或生产级连接治理;不要因为它只绑定回环地址,就把本机客户端一律视为可信。需要长期部署时,应另行设计资源限制和异常处理。

fd 数量、起始编号与共享监听对象规则见 sd_listen_fds 官方文档。如果以后增加多个 socket,不应继续假定“第一个必定是业务端口”,应按类型或名称识别,并重新定义数量约束。
在用户管理器中启用,并观察按需激活
读者准备好源码和两个单元后,可以在项目目录依次执行:
# 创建当前用户的单元和程序目录;单元内容需提前保存到对应位置 mkdir -p "$HOME/.config/systemd/user" "$HOME/.local/libexec" # 使用 libsystemd 的编译链接参数,生成前台工作程序 cc -Wall -Wextra socket-note-worker.c -o socket-note-worker $(pkg-config --cflags --libs libsystemd) # 只安装这个示例程序,不替换任何系统服务 install -m 755 socket-note-worker "$HOME/.local/libexec/socket-note-worker" # 加载两个新单元,只启用监听入口 systemctl --user daemon-reload systemctl --user enable --now socket-note.socket # 查看监听入口与业务进程各自的状态 systemctl --user status socket-note.socket socket-note.service # 用本机客户端发起连接;这是原始 TCP 文本,不是 HTTP nc 127.0.0.1 18081
应先看到 socket 处于监听状态;service 在首次连接前可能尚未启动,若已经被先前连接触发,也可能早已运行。客户端应收到代码定义的一行文本;如果没有,应根据日志确定问题,不能只凭端口可连接就宣布应用正常。
# 仅读取本示例用户级单元的近期日志,排查路径、链接或 fd 错误 journalctl --user -u socket-note.socket -u socket-note.service -n 40 --no-pager
用户管理器是否在退出登录后继续运行,取决于系统对该用户会话的管理策略,不能把这个实验等同于系统级常驻部署。所有命令都必须带 --user,避免把同名用户级与系统级单元混为一谈。
常见异常对应的是哪一层
| 现象 | 优先确认 | 不应直接采取的动作 |
|---|---|---|
| socket 无法启动 | 监听地址、端口占用和单元日志 | 杀掉不属于本项目的占用进程 |
| 程序报告没有继承 fd | 是否由 socket 激活、是否直接执行了二进制 | 手工伪造 LISTEN_PID 等环境变量 |
| 收到 fd 却类型不符 | Accept 模式与 socket 类型是否匹配 | 对任意 fd 盲目 accept |
| 程序再次 bind 时地址被占用 | 是否遗漏了继承分支 | 为了重复 bind 随意修改端口复用设置 |
不使用 shell 包装器启动示例,避免包装器未 exec 而改变实际接收进程,使 LISTEN_PID 与进程不匹配。直接执行二进制时返回非预期 fd 数量是这个示例的设计行为,不是要求添加一个隐藏监听分支。
结束实验时,同时考虑 socket 和 service
# 停止监听入口和已运行的业务进程,避免只停服务后又被连接激活 systemctl --user stop socket-note.socket socket-note.service # 取消这个示例的用户级自动启用,不删除任何其他单元 systemctl --user disable socket-note.socket
保留源码和单元便于继续阅读;这里不提供广泛删除目录的清理命令。若只修改 service 后重启进程,socket 仍运行时通常仍是原来的监听对象,这种引用保留不是“旧进程还活着”。它也不保证零丢失:排队容量、应用状态和客户端超时仍会影响请求。
把 Accept 改成 yes,代码可以不变吗?
不可以。yes 模式是按连接实例化服务,通常需要对应模板单元,并处理已连接套接字;本例明确要求监听状态,会拒绝这种交付方式。
socket activation 能让任意旧程序自动兼容吗?
不能。程序必须理解所用 fd 交付协议,或者明确支持另一种约定。只写两个单元,不会自动改造原来坚持自行 bind 的业务代码。
为什么停止 service 后仍然能连接端口?
因为监听生命周期属于 socket 单元。需要彻底结束入口时,应明确停止 socket;需要连业务进程一并结束时,同时停止 service。理解这两个生命周期,比把“端口可连接”当成“业务进程存在”的证据更重要。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
194 收藏
-
文章 · linux | 1小时前 | 定时任务 · Linux · 日志排查 · Linux journalctl OnCalendar Persistent systemd timer cron迁移253 收藏
-
331 收藏
-
420 收藏
-
204 收藏
-
443 收藏
-
452 收藏
-
129 收藏
-
394 收藏
-
211 收藏
-
275 收藏
-
180 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习