Python mDNS 的本地服务发现

1次阅读

zeroconf是当前最靠谱的python mdns方案,因其原生封装dns-sd、跨平台支持、自动处理去重/ttl/缓存/冲突,而dnspython手动实现易出错且不完整。

Python mDNS 的本地服务发现

为什么 zeroconf 是当前最靠谱的选择

Python 原生不带 mDNS 实现,socket 层手动发包既难调试又容易被防火墙/网络策略拦截。社区里真正稳定可用的只有 zeroconf(即 python-zeroconf),它封装了 DNS-SD 协议细节,跨平台支持 macos / linux / windows,且默认使用多播地址 224.0.0.251 和端口 5353,符合 RFC 6762。

别碰 dnspython + 手动构造 mDNS 查询包——它不处理服务实例名解析、TXT 记录解码、缓存逻辑或冲突检测,上线后大概率发现服务“时有时无”。

  • zeroconf 自动处理服务名去重、TTL 刷新、缓存验证,避免重复注册或陈旧记录残留
  • Windows 上需额外安装 pywin32(否则 ServiceBrowser 可能静默失败)
  • macOS 默认启用 avahi-daemon 冲突,建议启动前加 zeroconf=Zeroconf(ip_version=IPVersion.V4Only)

注册服务时 host 参数填什么最安全

填错 host 是服务不可见的第一大原因。它不是“本机 IP”,而是你要对外暴露服务的主机名(hostname),且必须能被局域网其他设备通过 mDNS 解析到 —— 通常就是本机的 .local 名。

运行 hostname 命令看输出(比如 mylaptop.local),就用这个值;如果返回的是纯主机名(如 mylaptop),得手动补 .local。千万别填 127.0.0.1localhost,它们在 mDNS 网络里不生效。

立即学习Python免费学习笔记(深入)”;

  • 服务注册示例:ServiceInfo(type_="_http._tcp.local.", name="Web Server._http._tcp.local.", addresses=[inet_aton("192.168.1.10")], port=8000, host="mylaptop.local")
  • 若用 get_all_addresses() 自动获取 IP,注意它可能返回多个(如 docker 网桥、vEthernet),要过滤掉非物理网卡地址
  • android 设备默认不响应 mDNS 查询,ios/macOS 则没问题;测试务必用 Mac 或 Linux 机器当客户端

ServiceBrowser 收不到回调?先查这三件事

常见现象是服务明明注册成功,但监听端收不到 add_service 回调。这不是代码写错了,而是网络层或权限问题。

  • 确认 Python 进程有组播接收权限:Linux 上执行 ip link show | grep -A2 multicast,确保主网卡 multicast 标志为 ON
  • 检查防火墙:macOS 的「防火墙选项」里勾选「允许远程登录」会意外阻断 mDNS;Linux 上临时关掉 ufw 测试
  • ServiceBrowser 启动后需保持线程活跃(不能立即退出),建议用 time.sleep(30)signal.pause() 阻塞,而非 input()(后者在某些 ide 中会中断信号)

服务名含空格或中文?立刻改掉

mDNS 协议对服务实例名(name 参数)有严格限制:只允许 ASCII 字母、数字、连字符、下划线,且首尾不能是连字符或下划线。空格、中文、括号、点号都会导致注册失败或解析异常,但错误不抛出,只静默忽略。

比如 "My Web Server._http._tcp.local." 会被截断成 "My""服务器._http._tcp.local." 在 macOS 上可能注册成功但 Android 客户端完全看不见。

  • 安全做法:用 re.sub(r"[^a-zA-Z0-9-_]", "-", name).strip("-_") 清洗服务名
  • TXT 记录值可以含 UTF-8(如 {"version": "1.2.0", "desc": "测试服务"}),但 key 必须是 ASCII
  • 服务类型(type_)末尾的 .local. 不能省略,少一个点就查不到

最麻烦的其实是网络拓扑:同一台路由器下的设备能通,但跨 VLAN、AP 桥接、或用了企业级交换机(默认禁用 IGMP snooping)时,mDNS 组播包根本传不出子网。这时候再怎么调代码都没用。

text=ZqhQzanResources