设备档案是三次调用拼出来的:信息、能力、服务清单,三样各管一件事。
其中 GetCapabilities 最实用——它一次告诉你每个 service 的地址,
所以永远不要硬编码 /onvif/media_service。
上一节跑通了闭环,这一节把「取信息」这件事做完整。
很多人只调 GetDeviceInformation 就以为拿到了设备档案,
其实那只是「铭牌」,不是「能力」。
一、三次调用各答什么
| 操作 | 回答什么 | 典型用途 |
|---|---|---|
GetDeviceInformation |
厂家/型号/固件/序列号 | 台账、工单 |
GetCapabilities |
各 service 的地址 | 决定后面调哪些 |
GetServices |
完整服务清单 + 版本 | 挑版本、查开关 |
第一行是「它是什么」,
第二、三行是「它能干什么」——做资产管理系统只需要第一行,做客户端必须三行都要。
第二行那个「地址」是最容易被浪费的信息:
很多人拿到之后就扔了,然后自己在代码里写死 /onvif/media_service——
换个厂家路径变了,整个客户端就不能用。
正确做法是「先问地址,再去调」,这样同一份代码能适配所有厂家。
二、请求大小由信封决定
实测三次调用的请求字节是 424、414、406——几乎一样。
原因很简单:请求体都是空的(<tds:GetXxx/>),差别只在操作名那十几个字符。
| 组成部分 | 字节 | 占比 |
|---|---|---|
| HTTP 头 | 约 150 | 35% |
| SOAP 信封与命名空间 | 约 240 | 56% |
| 业务元素 | 十几到几十 | 不到 5% |
第三行那个「不到 5%」在第七章讲过(412 字节里意图只占 27),
这里又验证了一次——所以「优化带宽」这个目标,在 ONVIF 里几乎不是优化业务元素能解决的。
真正能省的是「减少调用次数」和「复用 TCP 连接」,而不是「把 XML 写短一点」。
第二行那个 56% 是命名空间:每个请求都要带 5 个左右的 xmlns,这是 SOAP 的固有开销,
省不掉。
三、响应大小差别很大
实测响应是 549、782、1255——差别是三倍,因为内容量不同:
| 响应 | 字节 | 为什么 |
|---|---|---|
GetDeviceInformation |
549 | 五个字段 |
GetCapabilities |
782 | 三组能力,各带地址 |
GetServices |
1255 | 每个服务一条,还有版本号 |
第三行最大是必然的:它要把 11 组 service 全列一遍,
每组还带 Namespace、XAddr、Version 三样。
所以「先调 GetServices 看有哪些服务」这个做法,
在设备多的时候是有代价的——一台设备 1.2 KB,一千台就是 1.2 MB,而且每次轮询都花这个钱。
工程上的折中:发现阶段调一次、存下来,轮询阶段不再调。
四、地址里可能藏着坑
GetCapabilities 返回的地址有三种陷阱:
| 形态 | 问题 | 怎么处理 |
|---|---|---|
| 端口与 device 不同 | 套用 device 的端口就连不上 | 按返回值用 |
| 主机名而不是 IP | DNS 解析不了就失败 | 能解析才用,否则回退 |
https 开头 |
客户端没配 SSL 就失败 | 检查是否编进 SSL |
第一行是真事故:有的厂家把 media 放在 8080、device 放在 80,
而很多人写代码时只记了 device 的端口,于是 GetProfiles 一直超时。
第二行更隐蔽:有的设备返回 http://IPC-2000/onvif/media_service(用主机名),
而你的机器解析不了这个名字——表现是「发现成功、能力取到、但后续全超时」。
第三行在「设备开了 HTTPS 但客户端没编 SSL」时出现,而症状是「连接被重置」,
看起来像网络问题。
五、动手 Demo:设备档案是三次调用拼出来的
Demo 连调三次,量请求与响应字节,再把 GetCapabilities 里的地址抽出来。
重点看三个地址的端口是不是一样的——这一条能省掉很多排查。
# 三次调用,量请求与响应
for op in ("GetDeviceInformation", "GetCapabilities", "GetServices"):
t0 = time.perf_counter()
_a, b, n = S.call(port, "device", op)
ms = (time.perf_counter() - t0) * 1000
print(op, n, len(b.encode()), "%.2f ms" % ms)
# 从 Capabilities 里抽每个 service 的 XAddr
for el in ET.fromstring(bodies["GetCapabilities"]).iter():
addr = el.find(".//{%s}XAddr" % S.NS_TT)
if addr is not None:
print(el.tag.split("}")[1], addr.text)
实跑输出:
--------------------------------------------------------------------
设备档案是三次调用拼出来的
--------------------------------------------------------------------
**`GetDeviceInformation` 只给你五个字段**,
**想知道「这台设备能干什么」,还得问另外两个**:
操作 回答什么 典型用途
---------------------------------------------------------------------------
`GetDeviceInformation` 厂家/型号/固件/序列号 **台账**
`GetCapabilities` **各 service 的地址** **决定后面调哪些**
`GetServices` **完整服务清单 + 版本** 挑版本、查开关
**第二行最实用**:**它一次告诉你媒体、云台、事件各自的服务地址**,
**不用猜路径、不用硬编码 `/onvif/media_service`。**
**很多现场问题就出在硬编码**:换个厂家路径就变了,
**正确做法是「先问地址,再去调」。**
**三次调用实测**:
操作 请求字节 响应字节 耗时
---------------------------------------------------------------
`GetDeviceInformation` 424 549 1.11 毫秒
`GetCapabilities` 414 782 0.85 毫秒
`GetServices` 406 1255 0.68 毫秒
**第二列三行几乎一样**,因为请求体都是空的(`<tds:GetXxx/>`),
**差别只在操作名**——**请求大小由「信封」决定,不由「操作」决定。**
**第三列就不同了**:`GetServices` 的响应最大,因为它把每个服务都列一遍。
**第四列别当真**:同一台机器上三次调用抖到十倍是常事,因为每个请求都新建 TCP 连接,
还要等线程调度。
**`GetCapabilities` 里真正要抄下来的就这几行地址**:
Device → http://127.0.0.1:52707/onvif/device_service
Media → http://127.0.0.1:52707/onvif/media_service
PTZ → http://127.0.0.1:52707/onvif/ptz_service
**注意这些地址的端口与路径**:
**它们可能和 `device_service` 不同**(有的厂家分开端口),
**所以「把 device 的地址套给 media 用」这种写法迟早出事。**
**正确姿势**:**发现 → 取 capabilities → 从里面拿每个 service 的地址 → 再调。**
第一段的输出里第三列才是重点:请求字节 424、414、406,
几乎一样——因为它们都是「空请求体」,差别只在操作名。
这个现象可以用来判断「请求大小是否合理」:
如果你写出来的请求有 800 字节,说明多带了不该带的元素。
第二段那个 el.find 要带命名空间({...}XAddr),
这是 ElementTree 的硬要求——直接写 "XAddr" 一个都找不到。
输出里那三个地址的端口是一样的(本机模拟器都指向同一个服务),但真实设备上不一定——
所以别把这个结果当成「端口总是一样」的证据。
这一节的表格里写「端口可能不同」是有现场依据的,不是推测。
30 秒实验
- 把
GetServices的响应按行拆开,数一下一共列了几个服务。 - 在
GetCapabilities里找Media之外的地址,看还有哪几个。 - 把三次调用的顺序换一下(先
GetServices),看耗时有没有变化。
常见误区
- 只调
GetDeviceInformation就以为拿到了设备档案。 那只是铭牌, 「能干什么」要GetCapabilities和GetServices。 - 把 device 的地址套给 media 用。 有的厂家两个 service 端口不同,套用会一直超时。
- 靠「把 XML 写短」来优化带宽。 业务元素不到请求的 5%, 真正能省的是「减少调用」和「复用连接」。
自测三问(点开对答案)
1. `GetCapabilities` 最实用的地方是什么? —— **它一次告诉你每个 service 的地址**,**所以不用猜路径、不用硬编码 `/onvif/media_service`。** 2. 为什么三次请求字节几乎一样? —— **因为请求体都是空的**,差别只在操作名那十几个字符。大小由信封决定,不由操作决定。 3. 「发现成功、能力取到、后续全超时」可能是为什么? —— **地址里用的是主机名,而本机解析不了**;**或者地址是 `https` 而客户端没编 SSL。**下一节讲鉴权:四种情况、一个反直觉的「失败也回 200」。
- ☐ (本节完,下一节 08.04 鉴权(认证))