Python 广播发现的 SSDP 协议

1次阅读

ssdp发现失败主因是端口占用、多播配置错误及防火墙拦截;需检查1900端口占用、绑定具体ip而非””、设置so_reuseaddr/so_reuseport、加入239.255.255.250多播组、放行udp 1900入向流量、设recv超时、正确解析httpu响应并校验url。

Python 广播发现的 SSDP 协议

SSDP 发现请求发不出去,socket 绑定失败怎么办

常见现象是调用 socket.bind(("", 1900))OSError: [errno 98] Address already in use,或压根没收到任何响应。SSDP 依赖 UDP 多播,端口 1900 是保留端口,系统可能被其他 UPnP 服务(比如 windows 的 SSDP Discovery Service、macos 的 mDNSResponder)占着。

实操建议:

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

  • 先确认端口占用:lsof -i :1900(macOS/linux)或 netstat -ano | findstr :1900(Windows),杀掉无关进程
  • 绑定时别用 ""(即 INADDR_ANY),改用具体本地多播接口 IP,比如 "192.168.1.100";否则在多网卡机器上容易选错接口
  • 必须设置 socket.SO_REUSEADDR,部分系统还要求 SO_REUSEPORT(Linux/macOS),否则 bind 失败概率极高
  • 发送目标地址必须是 SSDP 多播地址 "239.255.255.250",端口固定为 1900,不能写错

发出了 M-SEARCH 却收不到响应,recvfrom 一直阻塞

不是服务没响应,大概率是 socket 没正确加入多播组,或者防火墙拦截了入向 UDP 包。SSDP 响应是从设备的随机端口发回的单播包,但很多实现(尤其 python 示例)误以为响应也在多播地址上,结果监听错了地方。

实操建议:

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

  • 接收 socket 必须调用 setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, ...) 加入 239.255.255.250 组,否则收不到 M-SEARCH 请求(虽然你发的是请求,但有些设备会把响应也发到多播组)
  • 更关键的是:响应是单播的,所以你得确保本机 UDP 端口 1900 对外开放——检查防火墙是否放行入向 UDP 1900,特别是 Windows Defender 防火墙默认会拦
  • recvfrom 超时一定要设,比如 sock.settimeout(3),不然卡死在无响应场景下
  • 不要只监听一个接口;如果机器有多个网卡(比如 WiFi + 以太网),需为每个活跃接口分别 bind + join group

收到响应但解析失败,location 字段为空或格式异常

SSDP 响应是 HTTPU(HTTP over UDP)格式,但不是完整 HTTP 报文:没有状态行、头字段大小写不敏感、换行可能是 rnn,且某些设备(如老款路由器)会漏掉 LOCATION: 或写成 location:,甚至夹带不可见字符。

实操建议:

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

  • 别用标准 http.client 解析响应;直接按行 split,对每行做 strip().lower().startswith("location:") 判断
  • 提取 URL 后,务必用 urllib.parse.urlparse() 校验 scheme 和 netloc,有些设备返回 LOCATION: http:// 后面直接断了
  • 注意 CRLF 和 LF 混用:用 response_bytes.replace(b"rn", b"n").split(b"n") 统一换行再处理
  • 部分设备(如某些 iot 插座)会在响应里塞多个 LOCATION,取第一个非空、能 parse 成合法 URL 的即可

为什么用 asyncio 写 SSDP 发现反而更难稳定

异步本身不解决 SSDP 的本质问题,反而容易掩盖 socket 配置错误。比如 asyncio.DatagramProtocol 默认不帮你 setsockopt,也不自动 join 多播组,一旦忘了手动配,就彻底收不到包;而且 asyncio 的 UDP transport 在不同 Python 版本里对多播支持不一致(3.8+ 更稳,3.7 及以前有已知 bug)。

实操建议:

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

  • 除非整个项目已是 asyncio 架构,否则优先用同步 socket——简单、可控、调试直观
  • 如果坚持用 asyncio,务必在 connection_made 里手动调用 transport.get_extra_info("socket").setsockopt(...) 设置多播选项
  • 避免在协程里反复创建/关闭 socket;SSDP 探测是短时密集操作,复用 socket 比每次新建更可靠
  • 别信“async 自动处理超时”——UDP 丢包率高,仍要自己控制重试次数和间隔(比如最多发 3 次,间隔 0.5s)

多播地址、端口、TTL、套接字选项这四样,只要有一样配错,SSDP 就静默失效。最容易被忽略的是 TTL —— 默认是 1,跨子网发现必须显式设为 2 或更高,但多数示例根本不提这一句。

text=ZqhQzanResources