登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

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 文件名和实际服务名不一致时,关联关系仍然清楚。

socket unit、ListenDatagram、UDP套接字和服务单元的静态关系图
图1:ListenDatagram 配置结构图,展示 socket unit 如何声明 UDP 地址并关联服务单元。

让服务从 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()。

LISTEN_FDS、fd3、socket.fromfd和UDP服务循环的静态关系图
图2:文件描述符交接结构图,展示服务如何从 systemd 接收 fd 3 并读取 UDP 数据报。

配置对应的 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= 并重新设计服务的读取循环。
声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>