当前位置:首页 > 文章列表 > 文章 > linux > systemd.socket 怎样继承监听套接字

systemd.socket 怎样继承监听套接字

来源:17golang原创 2026-10-25 14:01:17 0浏览 收藏

我第一次把服务改成 socket activation 时,最容易混淆的不是端口,而是“到底是谁拥有这个套接字”。答案是:systemd.socket 单元先创建并持有监听端点,服务启动时继承已经打开的文件描述符;服务不应该再按同一个地址重复 bind。只要把监听 FD 和连接 FD 分开理解,Accept=no 与 Accept=yes 的选择就清楚了。

官方地址:https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html

新写的守护进程通常从 Accept=no 开始:systemd 把监听套接字传给一个服务,应用自己 accept 多个连接;只有面向单连接、兼容 inetd 风格的程序,才优先考虑 Accept=yes。

先分清监听 FD 与连接 FD

.socket 文件描述“监听什么”,例如 TCP 地址、Unix socket 路径或 FIFO;.service 描述“启动谁”。同名的 demo.socket 默认会激活同名的 demo.service。systemd 在服务启动前准备好套接字,再把它们放进服务进程能读取的文件描述符集合中。

systemd.socket 的监听文件描述符在 Accept=no 和 Accept=yes 两种模式下交给服务的静态关系说明图
图1:systemd.socket、监听文件描述符与两种 Accept 模式的关系示意图,不是截图或运行证据。

这里有两个对象:

  • 监听文件描述符:还没有绑定某个客户端连接,应用要对它调用 accept。
  • 连接文件描述符:已经对应一个客户端连接,应用可以直接读写,通常不再负责接收下一条连接。

systemd 的 socket 文档说明,socket unit 可以按需启动服务;服务则必须支持 systemd 原生的文件描述符传递协议,或者使用 StandardInput=socket 这种传统 inetd 方式。本文下面的单元示例采用原生继承方式,应用侧仍要使用对应库或自己的 FD 读取逻辑。

用 Accept=no 让一个服务接管监听

我更愿意把 Accept=no 看成“把门口交给服务”,而不是“systemd 代替应用处理连接”。它是默认值:systemd 把所有监听套接字传给一个服务单元,服务自行 accept,然后把连接交给自己的处理循环。

[Unit]
# 让 socket 单元在服务之前准备好监听端点
Description=Demo listening socket

[Socket]
# 示例只监听本机 9090 端口,实际部署按需替换地址
ListenStream=127.0.0.1:9090
# 默认值为 no:服务接收监听 FD,而不是每个连接的 FD
Accept=no

[Install]
# 让 systemd 在启用 sockets.target 时拉起该 socket
WantedBy=sockets.target

保存为 /etc/systemd/system/demo.socket 后,通常还需要一个同名服务:

[Unit]
# 记录服务与 socket 的关系,便于阅读和排障
Description=Demo socket-activated service
Requires=demo.socket
After=demo.socket

[Service]
# 该程序必须能读取 systemd 传入的监听文件描述符
ExecStart=/usr/local/bin/demo --systemd-activation
Restart=on-failure

[Install]
# 直接启用服务不是必须的,socket unit 可以按需激活它
WantedBy=multi-user.target

应用侧的关键不是再次监听 127.0.0.1:9090,而是读取 systemd 约定的 FD。使用 sd-daemon 接口时,sd_listen_fds() 会检查 LISTEN_PID,再根据 LISTEN_FDS 从约定的起始 FD 读取数量;如果应用使用其他语言,则应使用该语言生态中兼容 socket activation 的库,或者准确实现同一约定。

用 Accept=yes 处理逐连接实例

Accept=yes 的行为完全不同:每个进入的连接都会生成一个服务实例,实例只拿到已连接的那个套接字。这样适合一次只处理一条连接、原本就是 inetd 风格的程序;新写的高并发服务通常不把它作为默认方案,因为每次连接都有额外的服务实例启动成本。

[Socket]
# 每个连接都激活一个服务实例
ListenStream=127.0.0.1:9091
Accept=yes
Service=demo-connection@.service

这里的服务名使用模板形式,例如 demo-connection@.service。实例化服务收到的是连接 FD,不是监听 FD;它不应该再次调用 accept。对于文件系统 Unix socket,服务也不要擅自 unlink systemd 管理的 socket 节点。要是程序并不理解这种逐连接生命周期,改回 Accept=no 通常更简单。

用 Sockets= 和 FileDescriptorName= 管好多套接字

同名约定适合最小配置,但多个入口往往需要更明确的契约。Service= 可以把 socket unit 指向一个不同名称的服务;服务单元的 Sockets= 则可以明确声明要继承哪些 socket unit。systemd 文档还说明,一个服务可以从多个 socket unit 收到文件描述符,但跨 socket unit 的传递顺序不应当被当成稳定协议。

[Socket]
# 外部 HTTP 入口由这个 socket unit 持有
ListenStream=127.0.0.1:8080
# 名称会帮助应用区分多个传入 FD
FileDescriptorName=web
Service=router.service

[Socket]
# 管理入口使用不同端口,保持同一个服务进程
ListenStream=127.0.0.1:9090
FileDescriptorName=admin
Service=router.service

应用若需要识别名称,可以读取 LISTEN_FDNAMES,或使用支持名称的 sd_listen_fds_with_names()。如果没有配置 FileDescriptorName=,systemd 会按 socket unit 名称生成默认名称(Accept=no 时包含 .socket 后缀)。这比依赖“第一个永远是 web、第二个永远是 admin”更稳妥。

多个 socket unit 通过 Sockets 和文件描述符名称传给服务进程的静态关系说明图
图2:多个 socket unit 通过 Sockets=、LISTEN_FDS 与 LISTEN_FDNAMES 交给服务进程的关系示意图,不是截图或运行证据。

把启动和维护动作固定下来

单元文件写好后,常用动作可以保持很小:

# 让 systemd 重新读取刚修改的单元文件
sudo systemctl daemon-reload
# 先启动 socket,让监听端点由 systemd 持有
sudo systemctl enable --now demo.socket
# 查看 socket 是否处于监听状态
sudo systemctl status demo.socket
# 需要停用时先停 socket,避免新的连接继续触发服务
sudo systemctl disable --now demo.socket

排障时先看 socket unit 是否成功绑定,再看 service 是否被触发,最后确认应用是否真的消费了继承来的 FD。若服务日志出现“地址已被使用”,第一反应不应是换端口,而是检查应用是否又执行了一次 bind;若服务启动了却没有流量,检查它读取的是监听 FD 还是连接 FD,以及是否正确处理了 LISTEN_FDS。

我会这样做取舍

场景优先选择原因
新写的长期运行服务Accept=no一个服务掌握监听循环,连接处理和并发策略留在应用内。
单连接、inetd 风格程序Accept=yes每个实例只关心一个已连接套接字,改造边界更小。
一个进程接收多个入口Sockets= + FileDescriptorName=用明确名称识别 FD,避免依赖跨 unit 的顺序。

真正需要记住的是所有权:systemd 负责创建、持有和在激活时传递 socket;应用负责按激活协议拿到 FD,并决定怎样 accept、读写和关闭。只要这条边界没有被打破,systemd.socket 就不再像“神秘地替服务接管端口”,而是一份清晰的启动契约。

相关问题

systemd.socket 和 systemd.service 必须同名吗?

不必须。默认同名会自动关联;也可以在 socket unit 使用 Service= 指向其他服务,在 service unit 使用 Sockets= 明确继承来源。

Accept=no 时应用还要自己 accept 吗?

要。systemd 传入的是监听 FD,应用要用自己的连接循环接收客户端。

多个 FD 能只靠编号区分吗?

不建议把跨 socket unit 的顺序当作长期契约。为每个入口设置 FileDescriptorName=,再读取名称信息更可靠。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
systemd.path 怎样按文件变化触发服务systemd.path 怎样按文件变化触发服务
上一篇
systemd.path 怎样按文件变化触发服务
systemd-path 怎么查询当前用户的配置目录
下一篇
systemd-path 怎么查询当前用户的配置目录
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    686次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    701次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    757次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    719次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    557次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码