WS-Discovery 就是摄像机版的「谁在」:客户端组播问一句,设备单播答一句。 五种消息里日常只用两种,而 Probe 报文 630 字节里真正有用的只有 26 个字符。
08.01 把代码生成好了,08.02 开始用它干第一件事:找到设备。 这件事看着简单,其实是整个 ONVIF 里唯一「不需要知道地址」的操作—— 而后面所有操作都需要知道地址。 所以发现是「入口」,也是唯一走 UDP 的一步。
一、五种消息,两套机制
| 消息 | 谁发 | 什么时候 |
|---|---|---|
Hello |
设备 | 上线时主动喊一声 |
Bye |
设备 | 下线前喊一声 |
Probe |
客户端 | 「谁在?」组播 |
ProbeMatches |
设备 | 「我在」单播回 |
Resolve |
客户端 | 按名字再问细节 |
第一行和第三行是两套相反的机制:
Hello 是推送(设备主动报),Probe 是拉取(客户端主动问)。
两者都要支持:只实现 Probe 会漏掉「启动那一刻正在喊」的设备,
只等 Hello 会在设备早就上线时一直等不到。
工程上的做法通常是「启动时 Probe 一次,然后一直听 Hello」——把「存量」和「增量」分开处理。
第五行 Resolve 很少用:
它是在已经知道 EndpointReference 的情况下,按名字再问一次地址,日常几乎用不到。
二、630 字节里只有 26 个字符有用
一条 Probe 报文的体积分布很极端:
| 段 | 内容 | 占比 |
|---|---|---|
XML 声明与 4 个 xmlns |
命名空间 | 约一半 |
MessageID |
uuid:… 45 字符 |
7% |
To / Action |
固定值 | 15% |
Types |
dn:NetworkVideoTransmitter |
4% |
第四行那 26 个字符就是全部意图:「我要找网络视频发送器」。
如果 Types 留空,
意思是「什么都行」——
很多客户端为了图省事就这么写,代价是连打印机、路由器、智能电视都会回你一句。
这在实际现场是有影响的:会议室里一堆设备都开着 WS-Discovery,空 Types 会把它们全捞进来,
然后客户端要挨个发 GetDeviceInformation 才知道哪个是摄像机。
所以「填 Types」不是可选的优化,是必须做的过滤。
三、为什么发现用组播、之后用单播
这个问题看起来像「设计选择」,其实是「逻辑必然」:
| 阶段 | 知道对方地址吗 | 能用什么 |
|---|---|---|
| 发现之前 | 不知道 | 只能组播 |
| 发现之后 | 知道了(XAddrs) | 必须单播 |
第一行是「为什么不能用单播」:
单播要先知道地址,而「找地址」正是这一步要做的事——循环依赖。
第二行是「为什么不该继续用组播」:组播是「一对多」,发一条全组都收到,
而管理操作是「一对一」的——用组播去问某台设备的具体配置,等于每次操作都吵醒全网。
所以正确的分工是:组播只用在「谁在」这一句,之后全走单播。
这也解释了为什么 XAddrs 那么重要——它是「组播世界」到「单播世界」的唯一桥梁。
四、UDP 不可靠,靠什么补
WS-Discovery 跑在 UDP 上,而 UDP 会丢包。 规范给的补偿手段是「重传 + 随机延迟」:
| 机制 | 怎么做 | 解决什么 |
|---|---|---|
| 重传 | 同一条 Probe 发多次 | 丢包 |
| 随机延迟回应 | 设备等 0~500 毫秒再回 | 几百台同时回把客户端打爆 |
| 等一段时间收集 | 客户端等 1~2 秒再收工 | 慢设备还没来得及回 |
第二行是「防拥塞」:一次组播下去,一百台设备如果同时回,客户端的 socket 缓冲区会溢出, 表现是「只发现了三十台」。 第三行是「给慢设备留时间」:所以「Probe 发完立刻收工」是错的, 要等一两秒——这也是为什么「设备搜索」这个按钮点下去,界面要转一会儿。 第一行那个「重传」在实现上通常是发两三次, 间隔几百毫秒——丢一次还能发现,丢两次的概率就低多了。
五、动手 Demo:Probe 与 ProbeMatches 长什么样
Demo 拼一条真 Probe、起一台只应答 Probe 的假设备,看完整往返。
重点看 RelatesTo 那一行——它是把「问」和「答」配对的唯一凭据。
# ① 拼一条 Probe(真报文,字段一个不少)
xml, mid = D.probe_xml()
print(len(xml.encode("utf-8"))) # 630
# ② 起一台假设备,发过去、收回来
dev = D.Device().start()
_x, mid, got, ms = D.probe(dev.port)
h = D.harvest(got[0])
print(h["relates"] == mid, h["xaddr"])
实跑输出:
--------------------------------------------------------------------
Probe 与 ProbeMatches 长什么样
--------------------------------------------------------------------
**WS-Discovery 只有五种消息,而且日常只用前两种**:
消息 谁发 什么时候
---------------------------------------------------
`Hello` 设备 **上线时主动喊一声**
`Bye` 设备 下线前喊一声
`Probe` 客户端 **「谁在?」组播**
`ProbeMatches` 设备 **「我在」单播回**
`Resolve` 客户端 按名字再问一次细节
**第一行和第三行是两套机制**:
**`Hello` 是被动发现(设备主动报),`Probe` 是主动发现(客户端问)。**
**工程上两种都要**:**先到的设备靠 `Hello`,后来的客户端靠 `Probe`。**
**只实现 `Probe` 的客户端会漏掉「启动那一刻正在喊」的设备**,
**只等 `Hello` 的客户端在设备早就上线时会一直等不到。**
**这是本机拼出来的一条真 Probe:630 字节。**
**里面真正有用的只有 Types 那几个字符**:
段 内容 占多少
-----------------------------------------------------------------------------
XML 声明与 4 个 `xmlns` 命名空间,必须 462 字节
`MessageID` 这条请求的编号 **45 字符**
`To` 固定值 `urn:…:discovery` 44 字符
`Action` `…/discovery/Probe` 53 字符
`Types` **`dn:NetworkVideoTransmitter`** **26 字符**
**第五行就是全部意图**:**26 个字符,问「谁是网络视频发送器」。**
**如果把 `Types` 留空,意思是「什么都行」**——
**很多客户端就是这么写的,代价是连打印机、路由器都会回你一句。**
**设备端怎么回,看一次真跑**:
发出去 630 字节,回 1 条,用时 0.9 毫秒
`w:RelatesTo` 对上了吗:**对上了**
`d:XAddrs` → `http://127.0.0.1:61068/onvif/device_service`
`d:Scopes` → `onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/t`
`d:MetadataVersion` → `1`
**第二行是整个发现流程的钥匙**:`XAddrs` 是设备的服务地址。
**发现的目的不是「知道有台设备」,而是拿到这个地址**——
**拿到它,后面所有 SOAP 调用才有地方发。**
**这也是为什么组播只用在发现这一步**:
**发现之后的每一条请求都是单播**,组播只在「谁在」这一句里用。
那 630 字节是「括号套括号」堆出来的:
Envelope 套 Header 和 Body,Header 里三个元素,Body 里一个 Probe 套一个 Types。
真正发命令的地方只有 <d:Types> 那一个元素,其余全是「让 XML 合法的脚手架」。
第二段那个 h["relates"] == mid 是这一节的技术核心:
设备把收到的 MessageID 抄进 RelatesTo 回给你,
客户端靠它判断「这条回应是对应哪次询问」。
为什么需要它?因为组播是「一对多」:你发一条,十台设备回十条,
如果没有 RelatesTo,你没法知道哪条是回给「刚才那次」的。
最后那个 XAddrs 才是目的:
http://127.0.0.1:57239/onvif/device_service——拿到它,下一节就能发 SOAP 请求了。
30 秒实验
- 把
probe_xml()的types参数改成空串,看报文少几个字节。 - 起两台
D.Device(),同时发一条 Probe,看能收到几条回应。 - 把
Device(delay=0.5)打开,看「等 1 秒」这个设置会不会漏掉它。
常见误区
- 以为发现之后还能继续用组播。 组播只用在「谁在」这一句,之后所有管理操作都是单播。
Types留空。 那是「什么都行」,现场一堆设备会一起回你,客户端还得挨个问是啥。- Probe 发完立刻收工。 设备有随机延迟(0~500 毫秒),不等一下就收,会漏掉慢的那批。
自测三问(点开对答案)
1. 为什么发现必须用组播? —— **因为发现之前不知道对方地址**,单播要先有地址——**而「找地址」正是这一步的任务**,循环依赖。 2. `RelatesTo` 是干什么的? —— **把「回应」和「询问」配对**。组播是一对多,十条回应里要认出哪条是回给自己的。 3. Probe 里 `Types` 那 26 个字符是什么? —— **`dn:NetworkVideoTransmitter`**——「我要找网络视频发送器」。留空就是「什么都行」。下一节把单播、组播、广播放在一起比:三种地址,本机各通不通。
- ☐ (本节完,下一节 08.02.02 单播、多播(组播)和广播)