Linux systemd.socket ListenDatagram 怎样监听 UDP 套接字
来源:17golang原创
时间:2026-10-28 13:31:51 216浏览 收藏
想要用Linux systemd.socket的ListenDatagram监听UDP套接字,操作逻辑很清晰,你只需要创建对应.socket单元,在配置项里声明UDP类型的监听目标,关联绑定实际运行的服务单元,systemd就会提前完成套接字创建,再把收到的UDP报文交给对应业务进程处理。
只要正确配置ListenDatagram字段指定监听端口、IP或套接字文件路径,搭配对应设置了Accept=yes或关联服务的socket单元,就能完成UDP套接字监听,同时还能获得systemd托管的套接字常驻能力,服务崩溃重启也不会出现UDP端口长时间空窗的问题。
如果要让 systemd 负责创建 UDP 监听套接字,再按需启动一个处理程序,关键配置是 ListenDatagram=。它对应 SOCK_DGRAM,而不是 TCP 的 ListenStream=;对于 IP 地址,systemd 会先完成绑定,再把监听套接字交给匹配的服务。服务因此不应该再次对同一个地址调用 bind()。
官方地址:https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html
最小可用方案是:在.socket文件的[Socket]段写入ListenDatagram=127.0.0.1:9125,让同名.service从 systemd 传入的文件描述符读取数据报。UDP 场景不需要用Accept=yes为每个数据报创建服务实例。
先分清 ListenDatagram 和 ListenStream
ListenDatagram= 表示数据报套接字,IP 场景对应 UDP;ListenStream= 表示流式套接字,IP 场景对应 TCP。两者都可以触发服务,但应用读取方式和生命周期不同:UDP 保留一条条数据报的边界,TCP 则是连续字节流。
| 配置项 | IP 套接字类型 | 本文是否使用 |
|---|---|---|
ListenDatagram= | UDP / SOCK_DGRAM | 是,服务用 recvfrom() 读取 |
ListenStream= | TCP / SOCK_STREAM | 否,不能用来替代 UDP |
Accept= | 主要影响连接型套接字 | UDP 数据报场景下被忽略 |
编写 socket unit:让 systemd 创建 UDP 套接字
假设服务只在本机的 127.0.0.1:9125 接收数据报,先创建 /etc/systemd/system/udp-demo.socket:
[Unit]
Description=UDP demo socket
[Socket]
# 由 systemd 绑定本机 UDP 端口,服务不再重复 bind
ListenDatagram=127.0.0.1:9125
# 显式写出服务名,便于 unit 改名后仍保持关联
Service=udp-demo.service
[Install]
# 让管理员可以用 systemctl enable 启用这个 socket
WantedBy=sockets.target
如果省略 Service=,systemd 默认会寻找同名的 udp-demo.service。显式配置的好处是 unit 文件名和实际服务名不一致时,关联关系仍然清楚。

让服务从 fd 3 读取数据报
systemd 的原生 socket passing 会把监听套接字传给服务。常见约定是第一个激活套接字从文件描述符 3 开始,环境变量 LISTEN_FDS 表示数量,LISTEN_PID 用来确认这些描述符属于当前进程。
下面的 Python 示例只展示接收边界。它假定解释器和脚本路径已经由管理员准备好,代码不创建 UDP 监听端口,也不包含真实业务处理:
import os
import socket
# 只接受 systemd 传入的一个监听描述符,避免误把普通启动当成激活启动。
if os.environ.get("LISTEN_FDS") != "1":
raise RuntimeError("需要 systemd 传入一个 UDP 文件描述符")
# systemd 的第一个监听描述符从 3 开始;这里不重复 bind 地址和端口。
udp_socket = socket.fromfd(3, socket.AF_INET, socket.SOCK_DGRAM)
try:
while True:
# recvfrom 保留每个 UDP 数据报的边界,同时返回发送方地址。
payload, peer = udp_socket.recvfrom(4096)
print(f"收到 {peer} 的 {len(payload)} 字节数据")
finally:
# fromfd 会复制一个 Python 文件描述符,退出前关闭这份引用。
udp_socket.close()
生产服务还应处理退出信号、日志和异常;这里的重点是接口边界:服务接收由 systemd 已经创建好的套接字,不要把 127.0.0.1:9125 再交给 bind()。

配置对应的 service unit
在同一目录创建 /etc/systemd/system/udp-demo.service:
[Unit]
Description=UDP demo service
Requires=udp-demo.socket
After=udp-demo.socket
[Service]
Type=simple
# 该脚本读取 systemd 传入的 fd 3,不在进程内创建监听端口
ExecStart=/usr/bin/python3 /opt/udp-demo/server.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
Requires= 和 After= 表达的是服务与 socket unit 的依赖和顺序关系。socket unit 触发服务时,systemd 会把已经创建的套接字交给它;服务自己直接启动时,也要能正确处理 socket 不存在或未被激活的情况。
启用后怎样判断配置是否生效
配置文件保存后,先让 systemd 重新读取 unit,再启用并启动 socket:
# 重新读取 unit 文件,避免继续使用旧配置
sudo systemctl daemon-reload
# 让 socket 在启动目标中启用,并立即开始监听
sudo systemctl enable --now udp-demo.socket
# 查看 socket 的监听地址和关联服务
systemctl status udp-demo.socket
# 查看 unit 的完整属性,便于确认 ListenDatagram 和 Service
systemctl show udp-demo.socket -p Listen -p Service -p ActiveState
可见的成功条件是:udp-demo.socket 处于 active 状态,状态信息中能看到 127.0.0.1:9125,并且 Service 指向 udp-demo.service。向该端口发送一个 UDP 数据报后,服务应被激活并在日志中出现接收记录。
# 只发送一条本机测试数据报,不改变 socket unit 配置
printf 'hello systemd\n' | nc -u -w1 127.0.0.1 9125
# 查看本次服务激活产生的日志
journalctl -u udp-demo.service -n 20 --no-pager
四个容易误用的配置边界
Accept=yes 不是 UDP 并发开关
systemd 文档明确说明,Accept= 对数据报套接字会被忽略;UDP 由一个服务单元处理传入数据报。不要把 TCP 的每连接实例模型直接套到 UDP 上。
Backlog 不负责 UDP 数据报排队
Backlog= 影响尚未接受的流式或顺序分组连接,对本文的 UDP 数据报监听不是主要配置项。UDP 的接收缓冲、丢包和应用读取速度应从对应的 socket 与系统参数另行分析。
监听地址决定暴露范围
127.0.0.1:9125 只在本机回环地址监听;如果改成某个具体网卡地址,服务可达范围会随网卡和路由变化;如果改成通配地址,则应同时评估防火墙和访问控制。不要为了“先跑起来”盲目改成所有接口。
服务不要重复绑定
socket activation 的职责分工是 systemd 创建并持有监听套接字,服务读取并消费它。服务再次调用 bind(),通常会得到地址已被占用的错误,即便打开了某些复用选项,也会把职责和排障路径弄复杂。
结论与延伸问题
要用 Linux systemd.socket 监听 UDP,记住三件事:用 ListenDatagram=地址:端口 声明 SOCK_DGRAM,让匹配的 service 从 fd 3 接收套接字,并用 socket unit 的状态确认监听地址和服务关联。Accept=、Backlog= 不能替代 UDP 的接收模型;地址范围和服务是否重复 bind 才是最常见的排错重点。
相关问题:
- 如果服务启动后看不到 fd 3,先检查
LISTEN_FDS是否为 1,以及 service 是否确实由 socket unit 触发。 - 如果端口被占用,先确认是否还有其他进程或旧 unit 绑定了同一地址,不要立即增加复用选项。
- 如果要监听 TCP,应改用
ListenStream=并重新设计服务的读取循环。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
223 收藏
-
225 收藏
-
301 收藏
-
108 收藏
-
434 收藏
-
112 收藏
-
404 收藏
-
118 收藏
-
117 收藏
-
382 收藏
-
430 收藏
-
123 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习