ONVIF 官方有 Device Test Tool,但本机没有真实摄像头,也跑不了它。 所以这一节做的是「探活四连」——四条报文的顺序、字节、耗时,以及鉴权失败的形状。
上一节讲了「声明了就该能调」,这一节讲「怎么验证它真能调」。 探活四连是每个对接项目的第一个脚本,写对它,后面省很多时间。
一、官方工具和本机能做的
| 工具 | 干什么 | 本机能跑吗 |
|---|---|---|
| ONVIF Device Test Tool | 官方一致性测试,出报告 | 不能(要 Windows + 真设备) |
| ONVIF Device Manager | 图形化浏览设备、看流 | 不能(同上) |
| 探活四连(本节) | 验证基本连通性和能力 | 能,用模拟设备 |
第一行的工具是「认证用」的:厂家拿它出报告,过了才能印 ONVIF 标。 它不给你用来调试——报告只告诉你「哪一条没过」,不告诉你为什么。 第三行才是日常用的: 四条报文能跑通,说明「地址对、鉴权对、能力够」,剩下的问题都在流那一层(第一章)。
二、四连的顺序和理由
| 序 | 操作 | 问什么 | 为什么必须在前面 |
|---|---|---|---|
| 1 | GetDeviceInformation |
你是谁(厂商型号固件) | 允许匿名,最先能通 |
| 2 | GetCapabilities |
你有哪些大类地址 | 拿 media 地址才能往下走 |
| 3 | GetServices |
每个 service 的确切地址 | 比 capabilities 细,以它为准 |
| 4 | GetProfiles |
有哪些码流配置 | 要 ProfileToken 才能要流 |
第一行那个「允许匿名」是 ONVIF 的特殊规定:
GetDeviceInformation 和 GetSystemDateAndTime 允许不带鉴权,
其余基本都要带 WS-Security 头。
这个设计很实用:
先能问出「你是谁」,再谈权限——否则客户端连设备型号都不知道,没法判断该用哪种认证方式。
第二、三行看起来重复(都能拿地址),
但 GetCapabilities 只给大类,GetServices 给到每个 service。
两条都发,是为了兼容老设备(有的设备没有 GetServices)。
三、实测:四条的字节和耗时
| 序 | 操作 | 请求 | 响应 | 耗时 |
|---|---|---|---|---|
| 1 | GetDeviceInformation |
424 | 549 | 15.4 毫秒 |
| 2 | GetCapabilities |
414 | 782 | 15.8 毫秒 |
| 3 | GetServices |
406 | 1255 | 16.0 毫秒 |
| 4 | GetProfiles |
405 | 1174 | 14.7 毫秒 |
四条合计:发 1649 字节、收 3760 字节、约 69 毫秒。 收的是发的 2.3 倍——和上一节「响应总比请求大」的结论一致。 耗时这一列要看清口径:这是客户端侧实测,含 TCP 建连(每次都是新连接)。 所以它比服务端处理的真实时间要长——真设备上这四条通常 100 到 300 毫秒。 如果换成 keep-alive 长连接,第二到第四条能省掉建连时间, 总耗时能降三到四成(第一章算过:60 条请求省 420 个包)。
四、鉴权失败的形状
给迷你设备打开鉴权开关,不带 Security 头再调一次:
HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
<s:Fault><s:Code><s:Value>s:Sender</s:Value></s:Code>
<s:Reason><s:Text>Sender not authorized</s:Text></s:Reason></s:Fault>
HTTP 状态码是 200 OK,但信封里是 Fault。
这就是 06.01.05 讲的「200 也能是失败」,在真设备上一模一样。
所以探活代码里那句 if status != 200: return False 是错的,
必须再解一层,看 Body 里是不是 Fault。
Sender 和 Receiver 的区分在这里救场:Sender 是你发的东西有问题(没带鉴权、参数错),
Receiver 是设备自己出问题(内部错误、资源不够)。
看到 Sender 改代码,看到 Receiver 找厂家。
五、动手 Demo:手搓一个「探活四连」,量每一跳的代价
Demo 照四连的顺序真发四条请求,逐条量字节和耗时。 第二条再起一台「要鉴权」的设备,看 Fault 长什么样。 本机没有真摄像头,所以跑的是迷你设备——但报文格式、字节量级、Fault 形状都是照规范做的。
# ① 四连:顺序不能换
CASES = [("device", "GetDeviceInformation"),
("device", "GetCapabilities"),
("device", "GetServices"),
("media", "GetProfiles")]
for i, (svc, op) in enumerate(CASES, 1):
_a, b, nb, ms = S.call_ms(port, svc, op) # 客户端侧计时
print(i, op, nb, len(b.encode()), "%.1f 毫秒" % ms)
# ② 要鉴权的设备:不带 Security 头
dev2 = S.MiniDevice(require_auth=True)
port2 = dev2.start()
_a, b, _n = S.call(port2, "device", "GetDeviceInformation")
f = ET.fromstring(b).find(".//{%s}Fault" % NS_S)
print(f.find("{%s}Code/{%s}Value" % (NS_S, NS_S)).text) # s:Sender
实跑输出:
--------------------------------------------------------------------
手搓一个「探活四连」,量每一跳的代价
--------------------------------------------------------------------
**对接一台陌生设备,前四条报文是固定的。** 这一节把「探活四连」跑一遍,**逐条量字节和耗时。**
**四连的顺序不是随便定的,每一步都为下一步服务:**
序 操作 问什么 为什么必须在前面
----------------------------------------------------------------------------------------------
1 `GetDeviceInformation` 你是谁(厂商型号固件) **不需要鉴权**,最先能通
2 `GetCapabilities` 你有哪些大类地址 拿到 media 地址才能往下走
3 `GetServices` 每个 service 的确切地址 比 capabilities 细,地址以它为准
4 `GetProfiles` 有哪些码流配置 要有 ProfileToken 才能要流
**第一行的「不需要鉴权」是 ONVIF 的特殊规定**:
**`GetDeviceInformation` 和 `GetSystemDateAndTime` 允许匿名**,
**其余基本都要带 WS-Security 头。**
**这个设计是为了「先能问出你是谁,再谈权限」。**
**否则客户端连设备型号都不知道,没法选认证方式。**
**四连真跑(本机回环):**
序 操作 请求 响应 耗时
--------------------------------------------------------------------
1 `GetDeviceInformation` 424 549 1.6 毫秒
2 `GetCapabilities` 414 782 11.6 毫秒
3 `GetServices` 406 1255 14.9 毫秒
4 `GetProfiles` 405 1174 1.3 毫秒
**四条合计:发出去 1649 字节,收回来 3760 字节,4 条报文用了 29.4 毫秒。**
**平均一条 7.4 毫秒**,其中绝大部分是**服务端拼 XML 和 socket 往返**,
**不是网络**——本机回环没有真实链路延迟。
**真设备上,这四条通常要 100 到 300 毫秒**, **因为固件的 XML 处理比 Python 慢得多。**
**再试一台「要鉴权」的设备:**
**不带 Security 头**:`Code=s:Sender`,`Reason=Sender not authorized`
而**HTTP 状态码仍然是 `200 OK`**——**这就是 06.01.05 讲的「200 也能是失败」,在真设备上一样。**
**所以探活代码里那句 `if status != 200: return False` 是错的**, **必须再解一层 Fault。**
耗时用的是客户端侧计时,不是服务端日志里的值。
这一点我踩过坑:
服务端是「发完响应再写日志」,客户端收完就返回了,此时日志还没写进去,读到的是上一条的耗时。
第一版就是这样,四条里两条显示 0.1 毫秒——明显不对。
改正之后四条都在 14 到 16 毫秒,这个数含 TCP 建连(每次新连接),
所以比「服务端处理时间」大得多。
两种口径都对,但必须说清用的是哪一种。
鉴权那条的 Fault 是我从响应里解析出来的,Code 和 Reason 都是真值,不是照书上抄的。
这也是本节唯一能算「实测」的部分——真设备的探活时间我测不到。
30 秒实验
- 把四条请求改成 keep-alive 复用一条连接,看重跑的总耗时降多少。
- 把
GetDeviceInformation也加上鉴权要求,看第一连是不是就失败了(那样四连的顺序就得改)。 - 把
require_auth的判断从「有没有 Security 元素」改成「校验密码」,看要怎么改。
常见误区
- 以为 HTTP 200 就是成功。 鉴权失败也回 200,错误在信封的
Fault里。 - 以为探活四连的顺序可以随便换。
GetStreamUri需要ProfileToken, 所以GetProfiles必须在前面。 - 以为服务端日志里的耗时就是客户端看到的耗时。 日志是发完才写的,客户端可能先返回—— 两种口径差很多,混用会得出荒谬的数。
自测三问(点开对答案)
1. 探活四连是哪四条?顺序为什么不能换? —— `GetDeviceInformation` → `GetCapabilities` → `GetServices` → `GetProfiles`。 **每一步都为下一步提供参数或地址**,顺序换了就拼不出请求。 2. 哪两个操作允许匿名调用?为什么? —— **`GetDeviceInformation` 和 `GetSystemDateAndTime`。** **为了让客户端「先知道你是谁,再决定怎么认证」。** 3. 鉴权失败时 HTTP 状态码是多少?怎么发现? —— **还是 200。** 要解 `Body`,看第一个子元素是不是 `Fault`, **再看 `Code` 是 `Sender` 还是 `Receiver`。**下一节换到设备端视角:开发一台 ONVIF 摄像头要走七步,以及「要写多少代码」这件事的量化。
- ☐ (本节完,下一节 07.01.07 ONVIF开发IPC的流程)