📹 第七章 ONVIF协议原理解析
📄 一句话概括

ONVIF 的技术栈是四层叠加:HTTP 传、SOAP 装、WSDL 描述、Profile 分级。 一条最干净的请求里,真正表达「我要什么」的只有 27 个字节,不到 7%。

📝 本节和前面几节的关系

上一节讲了 ONVIF 的来由, 这一节拆它的技术栈——四层各管什么,以及叠起来之后一条报文长什么样。 这四层在第六章都单独讲过,这里是把它们摞起来看。

一、四层叠加,各管一段

层 技术 管什么 对应小节
传输 HTTP/1.1 把字节送到对端 06.01.02
装帧 SOAP 1.2 把「调用」装进信封 06.01.05
描述 WSDL 2.0 说明有哪些操作、参数什么类型 06.01.04
分级 Profile 声明「这个品类必须实现哪些」 07.01.04

前两层是「怎么发」,后两层是「发什么」。 这个划分很重要: 调不通时先判断是哪一半的问题—— 连不上是传输层,连上但报错是装帧层,报错说「不认识这个方法」是描述层。 第四层不出现在报文里,它只出现在产品彩页和一致性测试报告里。 所以它不影响「能不能调通」,只影响「该不该调」。

二、一条请求的字节去哪了

Demo 把一个 GetDeviceInformation 请求拆成三段数:

段 内容 字节 占比
HTTP 头 请求行 + 两个头 204 50%
SOAP 信封 Envelope + Body + 命名空间声明 181 44%
业务元素 <tds:GetDeviceInformation/> 27 7%

总请求 412 字节,真正表达意图的只有 27 字节。 这就是 RPC 的成本结构:大头是「怎么把这件事说清楚」,不是「说的事」。 第一行占了整整一半,而且全是固定开销——路径长一点、Host 长一点,它就涨。 所以「把设备地址配成域名而不是 IP」,请求会更大, 虽然只多几十字节,但在高频轮询里是实打实的。

三、加鉴权之后翻倍

ONVIF 的鉴权信息放在 SOAP 的 Header 里(WS-Security),不在 HTTP 头里。 所以它算在第二段,直接让信封翻倍:

版本 XML 字节 相对最小版
不带鉴权 208 1.00 倍
带 UsernameToken 451 2.17 倍

多出来的 243 字节,比整条原报文还长。 其中绝大头是那个 xmlns:wsse= 的命名空间 URI——光它就有七十多个字符。 这是 SOAP 命名空间机制的直接代价:每个新前缀都要把完整的 URI 抄一遍, 不能像 XML 的实体那样定义一次全局复用(除非用外部 DTD,而 SOAP 不允许)。 所以工程上有个共识:轮询能少发就少发,用事件订阅(PullMessages)代替定时查询, 这不只是省带宽,也是省设备的 CPU(07.02.11 讲)。

四、响应比请求大得多

请求 424 字节,响应 549 字节(同一台迷你设备)。 真实设备上这个比例更悬殊:GetProfiles 的响应经常上万字节, 因为一个 Profile 要列全套编码参数。

操作 请求 响应 倍数
GetDeviceInformation 424 549 1.3
GetCapabilities 414 782 1.9
GetServices 406 1255 3.1
GetProfiles 405 1174 2.9

第三、四行的倍数最高,因为它们返回的是列表——列表里每多一项就多一整段 XML。 这个不对称有个实际影响:设备的「上行带宽」比「下行」更吃紧,因为响应总是比请求大。 做多设备轮询时,瓶颈通常在设备侧的上行,不在客户端侧的下行。

五、动手 Demo:一个 ONVIF 请求的字节解剖

Demo 手工拼出两条请求(一条最小、一条带鉴权头), 分别量 HTTP 头、SOAP 信封、业务元素三段的字节数。 然后用真发的请求核对一遍——发出去的和算出来的要一致,不一致就说明算错了。

inner_min = "<tds:GetDeviceInformation/>"
xml_min = S.body_of("tds", inner_min)        # 信封 + 业务元素

# 带 WS-Security 的 UsernameToken(形状照 ONVIF,不带摘要)
inner_auth = ("<s:Header><wsse:Security xmlns:wsse="
               '"http://docs.oasis-open.org/wss/2004/01/'
               'oasis-200401-wss-wssecurity-secext-1.0.xsd">'
               "<wsse:UsernameToken><wsse:Username>admin</wsse:Username>"
               "<wsse:Password>12345</wsse:Password>"
               "</wsse:UsernameToken></wsse:Security></s:Header>"
               "<tds:GetDeviceInformation/>")

print(len(xml_min.encode()), len(xml_auth.encode()))    # 208 451
print(len(inner_min.encode()))                          # 27

实跑输出:

--------------------------------------------------------------------
一个 ONVIF 请求的字节解剖
--------------------------------------------------------------------
**前面第六章量过「包装」的代价,这一节把它拆得更细。**
  一个 `GetDeviceInformation` 请求,**字节都花在哪了。**

**先看「最干净」的一条请求,分三段数:**

  段                    内容                                字节      占比
  ------------------------------------------------------------------------------
  HTTP 头               请求行 + 两个头                     204       50%
  SOAP 信封             `Envelope` + `Body` + 命名空间声明  181       44%
  业务元素              `<tds:GetDeviceInformation/>`       27        7%

  **「业务元素」那段只有 27 个字节**,**总请求 412 字节,也就是说真正表达「我要什么」的不到 7%。**
  **这就是 RPC 的成本结构**:**大头是「怎么把这件事说清楚」,不是「说的事」。**

**加一个鉴权头,再看涨多少:**

  版本              XML 字节      相对最小版    多出来的是什么
  --------------------------------------------------------------------------------
  不带鉴权          208           1.00 倍       —
  带 UsernameToken  451           2.17 倍       **用户名 + 密码 + 一长串 xmlns**

  **鉴权头多花 243 字节,比整条 XML 原报文(208 字节)还长。**
  **其中绝大头是那个 `xmlns:wsse=` 的命名空间 URI**——**光它就有七十多个字符。**
  **这也是为什么每次轮询都带着它,流量会翻倍**:**设备越多、轮询越密,这笔开销越显眼。**

**看一眼真实发出去的两条请求,长度差多少:**

  `http://www.onvif.org/ver10/device/wsdl/GetDeviceInformation`
  **HTTP 头 204 字节 + 信封 208 字节 = 总 424 字节**

  **这个 424 字节的请求,拿回来的响应是 549 字节。**
  **一来一回,为了五个字段,花了将近一千字节。**

鉴权头里我用的是明文密码, 这只是为了量字节——真 ONVIF 要的是 PasswordDigest(Base64 的 SHA1 摘要), 那段密文还更长(08.01.05 讲怎么算)。 「业务元素 27 字节」这个数是纯元素本身,不含它前面的信封。 这种口径必须写清楚,否则「27 字节」和「412 字节」两个数字看起来会互相矛盾。 最后那段「真发一次,核对长度」是这一节的自检: 算出来 412,发出去 424,差的 12 字节是 Host 头里的端口—— 端口是运行时才分配的,算的时候还没定,所以算出来的少 12 个字节。 这个差异是我事后核对出来的,不是猜的。

30 秒实验

  1. 把 Host 从 IP 改成域名,重算请求字节,看涨多少。
  2. 给信封加一层 Header 但不放任何内容,看多几个字节(<s:Header/> 是 13 字节)。
  3. 把 GetProfiles 的响应字节除以请求字节,和 GetDeviceInformation 比一比。

常见误区

  1. 以为鉴权信息放在 HTTP 头里。 ONVIF 的鉴权在 SOAP 的 Header 里, 所以它算在 XML 那一半,不在 HTTP 那一半。
  2. 以为请求和响应差不多大。 响应通常是请求的 1.3 到 3 倍,返回列表的操作倍数最高。
  3. 以为算出来的字节数一定等于发出去的。 端口、时间戳、随机数这类运行时才定的字段会让两边对不上,差几十字节是正常的。
自测三问(点开对答案) 1. ONVIF 的四层技术栈分别是什么? —— HTTP(传输)、SOAP(装帧)、WSDL(描述)、Profile(分级)。 **Profile 不出现在报文里**,只出现在产品声明和测试报告里。 2. 为什么鉴权会让报文翻倍? —— 因为鉴权头里的 **`xmlns:wsse=` 命名空间 URI 有七十多个字符**, **加上用户名密码元素,一共 243 字节,比原报文还长。** 3. 响应总是比请求大,这对工程有什么影响? —— **设备的上行带宽比下行更吃紧。** 做多设备轮询时,**瓶颈在设备侧上行,不在客户端下行。**

下一节把「十几组接口」拆开看:ONVIF 到底有多少个 service,每组管什么。

相关笔记