gSOAP生成ONVIF框架C代码:从WSDL到设备管理客户端
2026/9/7 9:23:16 网站建设 项目流程

简介:以Linux平台为运行环境的gSOAP与ONVIF开发资源,主要面向需要对接网络视频设备的C语言开发者,也适用于物联网、安防监控等项目的快速落地。资源包本身就是一套利用gSOAP工具链生成的ONVIF框架C代码,能够把标准WSDL定义转换成可调用的客户端接口,省去手工编写SOAP与XML通信层的繁琐过程,开发者拿到后即可专注于业务逻辑。整个压缩包共12个文件,其中包含5个C源文件、5个头文件,分别负责客户端调用、消息编解码、类型定义等核心模块;另有1个命名空间映射文件和1个构建脚本,方便直接编译和后续集成。资源整体只有1.31MB,非常轻量,目前已有1677人浏览学习。压缩包内除了完整工程骨架,还覆盖了设备信息查询、获取RTSP码流地址等常用操作,能够让开发者快速理解gSOAP调用ONVIF服务的流程,并在此基础上扩展自己的安防或物联网应用。

1. 为什么选gSOAP来搭ONVIF的开发骨架

1.1 ONVIF协议到底在做什么

做摄像头或NVR对接的工程师,几乎都绕不开ONVIF。它全称是Open Network Video Interface Forum,定位是让不同厂家的网络摄像机、录像机、平台软件能互相通信。ONVIF规范里定义了大量接口:设备发现、设备信息获取、媒体配置、OSD叠加、事件订阅、云台控制、视频流拉取,等等。底层实现基于SOAP/XML over HTTP,设备发现走的是WS-Discovery的UDP组播。

如果完全自己手工拼SOAP报文,意味着要先构造XML、填命名空间、处理HTTP头、再解析响应XML。光是一个GetSystemDateAndTime调用就能写出一百多行字符串拼接代码。更麻烦的是,摄像头厂商对标准字段会有各种私有扩展,而ONVIF的WSDL和XSD又一大坨,手写解析逻辑很容易在边界情况下翻车。这也是为什么业界几乎统一采用代码生成器来做这层通信代码。

1.2 gSOAP把“API合同”翻译成C函数

gSOAP是一个开源工具集,核心能力是把Web Service的WSDL描述翻译成C或C++源码。WSDL相当于一份“接口合同”,里面写清楚了服务地址、方法名、入参出参类型、命名空间、SOAP动作。gSOAP里的wsdl2h负责读合同生成一个中间头文件,soapcpp2再读这个头文件生成完整的客户端/服务端骨架代码。

用上gSOAP之后,调用一个ONVIF接口就变成了直接调用一个C函数。框架自己处理XML序列化、命名空间前缀、类型映射、HTTP传输、错误码。这对于Linux下用C语言做视频设备服务端开发来说,省掉的不只是开发量,还有排查周期。标题里说的“生成ONVIF框架C代码”,本质上就是把devicemgmt.wsdlmedia.wsdl这些规范文件喂给gSOAP,然后得到一套可以直接编译进工程的.c/.h文件。

1.3 不选gSOAP,还有什么路可走

我在选型时也对比过其他路线,这里整理成一张表供参考:

方案优点缺点
纯手写HTTP+SOAP依赖最少,完全可控工作量极大,一个复杂接口能写一整天,兼容性难保证
厂家私有SDK对接某品牌非常省事绑定平台,换一家厂商就要重来,不适合做统一接入层
ONVIF官方测试工具用来验证协议正确性很方便只是测试工具,不是可集成的库
gSOAP生成代码一键生成,支持C/C++,客户端服务端都有生成代码量偏大,需要理解工具参数和结构

选择gSOAP不是因为它是万能的,而是在Linux下、以C语言为载体的ONVIF开发场景里,它是社区积累最厚、参考资料最多的一条路。

2. Linux下的环境准备:gSOAP安装与ONVIF规范文件整理

2.1 安装gSOAP工具链

在Ubuntu/Debian系系统里,安装很直接:

sudo apt update sudo apt install gsoap libgsoap-dev

装完检查一下版本:

wsdl2h -version soapcpp2 -version

如果系统是CentOS/RHEL,对应安装包叫gsoapgsoap-devel,用yum install gsoap gsoap-devel即可。版本太老建议直接去gSOAP官网下载源码编译,至少要用2.8.x,太老的版本对ONVIF里常见XSD类型支持不够好。

这里有个容易忽略的点:libgsoap-dev里提供的是gSOAP运行时头文件和链接库,但stdsoap2.c这个核心源文件不一定被正确安装到工程目录。很多人在编译自己代码时找不到soap_mallocsoap_new之类的符号,就是因为少了stdsoap2.c。后面我会专门讲这个问题。

2.2 获取并整理ONVIF的WSDL/XSD文件

gSOAP本身不带ONVIF协议描述,需要去ONVIF官网的规范下载页面拿WSDL。常用到的文件大概有这些:

  • devicemgmt.wsdl:设备管理,包含时间、用户、网络、固件升级等接口
  • media.wsdl:媒体配置,视频源、编码、OSD、Profile管理
  • events.wsdl:事件订阅和通知
  • deviceio.wsdl:IO口、串口、继电器
  • imaging.wsdl:成像参数,亮度、对比度、对焦
  • analytics.wsdl:智能分析配置

拿到压缩包后别急着解压就生成,先建一个干净的目录结构:

mkdir -p onvif_demo/schemas cd onvif_demo # 把下载的WSDL和XSD文件全部解压到 schemas/ 目录里

这里我建议按需导入。如果只是做设备接入和取流,只需要devicemgmt.wsdlmedia.wsdl;如果还要做告警消息推送,再把events.wsdl加进来。一口气把所有WSDL全塞进去,生成的代码会膨胀得很夸张,编译时间长,维护也难受。

2.3 用wsdl2h把WSDL转成中间头文件

先拿最核心的devicemgmt.wsdl演示,进入工程目录执行:

wsdl2h -c -s -o onvif.h schemas/devicemgmt.wsdl

参数含义:

  • -c:生成C语言代码而不是C++
  • -s:不使用STL。纯C环境下没有std::vector,结构体会变成指针加计数器的形式
  • -o:指定输出的头文件名

如果编译环境支持C++,并且你愿意用C++来写业务逻辑,可以去掉-s。不过纯C场景里,-s几乎是必加项。

wsdl2h执行完会生成一个onvif.h,里面是大量struct tds__xxxstruct tdds__xxx之类的类型定义。可以搜索验证一下:

grep "struct _tds__" onvif.h | head -20

如果WSDL文件之间互相引用XSD,可能会报“找不到schema”的错误。这时候就需要用-I指定schema目录,比如:

wsdl2h -c -s -I schemas -o onvif.h schemas/devicemgmt.wsdl

-I的作用是告诉工具去哪里找被引用的XSD文件,这在ONVIF多文件场景里几乎是必用的。

2.4 用soapcpp2生成C源码

中间头文件只是“半成品”,真正要编译的是soapcpp2生成的源码:

mkdir -p generated soapcpp2 -c -I /usr/share/gsoap/import -x -d generated onvif.h

这条命令的每个参数我都解释一下:

  • -c:生成C源码
  • -I /usr/share/gsoap/import:指定gSOAP内部的import路径,生成时会自动引用stdsoap2.hduration.h这类内置头文件
  • -x:不生成示例XML文件,省得输出一堆调试用报文
  • -d generated:输出到generated目录

执行完后,generated目录下会多出这么一堆文件:

soapStub.h # 数据结构定义,对应onvif.h里的类型 soapH.h # 函数声明,客户端、服务端接口都在这里 soapC.c # SOAP消息的序列化/反序列化实现 soapClient.c # 客户端请求实现 soapServer.c # 服务端框架实现 soapClientLib.c # 客户端可选库,打包时用 soapServerLib.c # 服务端可选库 *.nsmap # 命名空间映射表,XML序列化依赖它

如果只需要客户端代码,可以加-C参数;只需要服务端,加-S。实际开发里经常先按-C -S都生成,回头再裁剪。

3. 实操记录:生成代码并完成一个设备管理客户端

3.1 整理工程目录和编译文件

按上面步骤生成的代码还不能直接编译,因为缺少gSOAP运行时。解决办法是把stdsoap2.cstdsoap2.h复制到工程目录里:

cp /usr/share/gsoap/stdsoap2.c . cp /usr/share/gsoap/stdsoap2.h .

如果你的系统上没有这两个文件,说明安装包不完整,去gSOAP官网下载对应版本源码,在gsoap-2.8/gsoap/目录下能找到。最终目录结构大致这样:

onvif_demo/ ├── main.c ├── onvif.h ├── stdsoap2.c ├── stdsoap2.h ├── generated/ │ ├── soapStub.h │ ├── soapH.h │ ├── soapC.c │ ├── soapClient.c │ ├── soapServer.c │ └── ... └── schemas/ ├── devicemgmt.wsdl └── ...

3.2 用生成的客户端函数查询设备时间

设备时间接口GetSystemDateAndTime是最安全的调试入口,它不需要鉴权,只要设备支持ONVIF,匿名也能调。写一个main.c

#include "soapH.h" #include "soapStub.h" #include "devicemgmt.nsmap" // 以生成的nsmap文件名为准 #include <stdio.h> int main(void) { struct soap *soap = soap_new(); soap->recv_timeout = 5; soap->send_timeout = 5; // 函数名以 generated/soapH.h 里的声明为准 struct tds__GetSystemDateAndTime req; struct tds__GetSystemDateAndTimeResponse resp; soap_default___tds__GetSystemDateAndTime(soap, &req); if (soap_call___tds__GetSystemDateAndTime( soap, "http://192.168.1.100/onvif/device_service", "http://www.onvif.org/ver10/device/wsdl/GetSystemDateAndTime", &req, &resp) == SOAP_OK) { printf("设备返回时间: %d-%02d-%02dT%02d:%02d:%02d\n", resp.UTCDateTime->Date->Year, resp.UTCDateTime->Date->Month, resp.UTCDateTime->Date->Day, resp.UTCDateTime->Time->Hour, resp.UTCDateTime->Time->Minute, resp.UTCDateTime->Time->Second); } else { soap_print_fault(soap, stderr); } soap_destroy(soap); soap_end(soap); soap_free(soap); return 0; }

重点提醒:不同ONVIF版本生成的函数名不一定长一样,别死记我的命名。先执行一条命令确认实际生成的函数名:

grep "soap_call___tds__GetSystemDateAndTime" generated/soapH.h

没有这一步,照网上老教程抄完,编译大概率报“函数未定义”。我在这里吃过亏。

编译命令:

gcc -o onvif_time main.c generated/soapC.c generated/soapClient.c stdsoap2.c \ -I generated -I /usr/share/gsoap -lm

编译成功跑起来,能拿到设备时间,说明生成代码从编译到链路完全通了,后续接其他接口就是复制同样的模式。

3.3 服务端骨架:soapServer.c到底怎么用

做服务端生成的代码也一样,但处理思路反过来了。soapcpp2生成的soapServer.c里,每个ONVIF方法都会暴露成一个回调函数,比如:

SOAP_FMAC3 int SOAP_FMAC4 __tds__GetSystemDateAndTime( struct soap *soap, struct tds__GetSystemDateAndTime *tds__GetSystemDateAndTime, struct tds__GetSystemDateAndTimeResponse *tds__GetSystemDateAndTimeResponse) { // 填充 响应结构体字段 return SOAP_OK; }

你只需要实现这个函数,把响应结构体里的字段填完整。然后在一个HTTP服务循环里调用soap_serve(soap),框架就会自动根据请求里的SOAPAction找到对应的回调函数。换句话说,服务端开发最麻烦的“路由分发”和“XML解析”又被gSOAP包办了。

这种生成方式特别适合做虚拟摄像头或者ONVIF网关,面对上层平台时,自己是服务端;往下对接实际设备时,自己又变成客户端。

4. 常见问题与排查技巧

4.1 wsdl2h加载schema失败

最常见的报错是Error: Cannot open file 'common.xsd'或者Failed to read schemas/...。原因多半是引用路径不对。ONVIF的WSDL文件会引用很多外部XSD,如果XSD和WSDL不在同一个相对路径下,就会加载失败。

我的处理习惯是把所有WSDL和XSD全部平铺到同一个schema目录,然后用-I指过去。如果目录层级有严格依赖,也可以把所有文件放到一个目录里,反正XSD文件名基本不冲突。

如果还报错,可能是某些XSD引用了网络地址。这时候需要检查是不是在当前机器上无法访问外部资源。最稳妥的办法是把引用的XSD下载到本地,再统一指到本地目录。

4.2 生成代码臃肿,工程体积暴涨

soapcpp2默认会把头文件里所有定义全部生成一遍,哪怕你的业务只用到其中一个接口。一整套ONVIF规范生成下来,soapC.c动辄上万行,编译器处理起来非常慢。

更合理的做法是“按服务拆分”。比如只需要设备管理,就只对devicemgmt.wsdl生成;后面要media接口,再单独建一个media_onvif.h做二次生成。这个方法听着简单,但很多人为了省事一次性导入所有WSDL,最后陷在编译等待和符号冲突里。

4.3 链接期undefined reference

出现这种情况,九成是没带stdsoap2.c,或者带的gSOAP运行时和生成代码版本不一致。stdsoap2.c是整个gSOAP的运行时核心,soap_newsoap_malloc这些函数都在里面。编译时把它和soapC.csoapClient.c一起编进去:

gcc -o onvif_time main.c generated/soapC.c generated/soapClient.c stdsoap2.c ...

还有一个坑:某些发行版的gsoap工具版本和libgsoap-dev库版本对应不上。wsdl2hsoapcpp2是新版本,但stdsoap2.c是旧版本,编完后出现一堆奇怪的崩溃。保险做法是把工具版本和运行时版本统一,直接用同一个源码包里的二进制或源文件。

4.4 设备发现收不到组播响应

ONVIF设备发现走的是WS-Discovery,目标地址239.255.255.250:3702。gSOAP生成的代码主要负责SOAP接口,不负责处理设备发现的组播报文。很多人在这一步卡住,以为是代码问题,其实是网络环境问题。

先用tcpdump抓包确认报文有没有发出去:

tcpdump -i eth0 host 239.255.255.250 and port 3702

如果抓不到发出的Probe报文,检查网卡是否启用了多播;如果发出去了但没响应,大概率是设备防火墙或交换机隔离了组播流量。虚拟机环境里尤其容易出问题,把虚拟网卡模式改成桥接往往就好了。

5. 从“能跑”到“能商用”的几个心得

5.1 生成代码只是起点,线程和内存要自己管

gSOAP生成的代码默认不是线程安全的。启用多线程后,每个线程最好单独创建自己的struct soap实例,互不共享。如果一定要跨线程复用,需要用soap_copy()做一个副本,并且自己要处理好锁。

内存方面也有讲究。所有soap_malloc申请出来的内存都和soap实例绑定,调用soap_end()时会被统一释放。如果不注意生命周期,很容易出现请求处理完了但内存迟迟不回收的问题。我在压测虚拟摄像头服务端时,就是因为忘记在每次请求结束后调用soap_destroy()soap_end(),内存曲线一路往上飞。

5.2 再往后可以怎么扩展

生成完基础框架后,有几个方向是实际项目里一定会碰到的:

  • 鉴权:ONVIF多数接口需要WS-Security用户名令牌认证,gSOAP支持soap_wsse_add_UsernameTokenText这类函数,需要引入wsse.h并启用对应插件。
  • 实时流:ONVIF本身只负责会话和媒体配置,真正的视频流走RTSP。生成代码拿到RTSP地址后,再接一个RTSP库做拉流推流,整个链路就完整了。
  • 自定义扩展:如果设备厂商有私有能力,可以自己写扩展WSDL,继续喂给gSOAP生成,框架会把这些方法一并编进去。

说到底,gSOAP这套工作流并不复杂,真正花时间的反而是对ONVIF协议本身的理解。把wsdl2hsoapcpp2这两个工具用顺,后续不管对接哪个品牌的设备,你都会发现大家底层都长得差不多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询