拓冰建站拓冰建站
首页 / 资讯中心 / 正文

gSOAP生成ONVIF C代码:嵌入式Linux设备接入实战指南

简介在Linux环境下使用gSOAP生成ONVIF框架C代码是网络视频设备互操作开发中的重要环节。面向需要对接ONVIF协议、完成摄像头或NVR设备接入的C/C开发者资源围绕soapcpp2工具链提供一套可复用的完整工程解决从WSDL解析、代码生成到设备调用之间的衔接问题。包内含12个文件以5个C源文件与5个头文件为核心分别承担gSOAP客户端调用、XML序列化、stub接口定义等职责另附nsmap命名空间映射文件与makefile构建脚本压缩包仅1.31MB便于快速下载并集成到Linux项目中。已有1677人学习使用适合具备基础网络编程知识、正在研究视频监控或IoT设备接入的工程技术人员。借助资源中的生成示例可以清晰掌握soapcpp2生成产物的目录结构和调用关系理解GetDeviceInformation、GetStreamUri等ONVIF服务方法如何触发并能在实际项目中快速实现设备信息读取、RTSP地址获取与后续二次开发。 干过安防、做过IPC网络摄像头开发的朋友对ONVIF协议应该都不陌生。只要是做设备端接入、对接NVR或者平台第一关就是搞定ONVIF的框架代码。自己手写整个协议栈不现实行业里最常见的方案就是用gSOAP在Linux环境下把ONVIF官方提供的WSDL文件直接生成一套C语言框架然后在这套代码上填充自己的业务逻辑。这篇文章把我实际操作中从安装gSOAP、下载WSDL、生成代码到编译运行、排查问题的一套完整流程整理出来。不管你是刚接触嵌入式Linux开发的新手还是被ONVIF对接折磨过的老手这套流程都能直接照搬避免走弯路。1. 整体设计思路为什么选gSOAP以及它到底做了什么1.1 gSOAP的核心价值把XML解析这种脏活累活接过去ONVIF协议的底层通信基于SOAPXML格式的远程调用协议。如果你自己裸写ONVIF需要处理的事情非常繁琐XML序列化、反序列化、SOAP信封构造、HTTP传输、鉴权哈希计算……一套搞下来代码量非常大而且极其容易在XML细节上出错。gSOAP做的事情简单说就是“用WSDL/头文件自动生成SOAP通信代码”。它自带两个工具wsdl2h把WSDL文件Web服务描述文件ONVIF官方提供解析成一个C/C头文件这个头文件里定义了服务端口、请求/响应结构体。soapcpp2把这个头文件再翻译成完整的C/C通信框架源码包括客户端和服务端的存根stub与骨架skeleton还有XML绑定、HTTP传输这些底层实现。用生活化的比喻来说你不需要自己从零砌墙、布线、做防水gSOAP直接把一个带装修的毛坯房交给你你只需要决定每个房间怎么摆放家具——也就是往哪几个回调函数里填自己的业务逻辑。1.2 ONVIF协议标准里有哪些服务需要生成ONVIF标准定义了多种服务各自负责不同的功能域。实际开发中设备端最常接触这几个服务名命名空间前缀主要用途Devicetds设备信息、时间、网络、用户管理Mediatrt媒体流配置、Profile管理PTZtptz云台控制、预置位操作Eventtev事件订阅与通知报警、移动侦测Recording / Searchtrctse录像查询、回放控制Discoverywsdd设备发现广播探测用gSOAP生成代码时你可以一次性导入所有WSDL也可以按需只生成自己需要的服务。第一次做的时候我建议把所有服务都生成出来后续编译不过再裁剪这样省得反复跑工具。1.3 生成后的代码长什么样执行完整生成流程后在当前目录下会得到一堆文件。核心的几类是soapH.h、soapStub.h序列化与类型定义头文件包含了所有从ONVIF映射过来的结构体。soapClient.c/soapServer.c客户端调用与服务端处理框架。soapC.cXML序列化/反序列化的实现核心文件必不可少。wsdl2h生成的头文件比如onvif.h定义了服务端口结构例如struct SOAP_ENV__Envelope和各种_tds__GetDeviceInformation类型的请求/响应结构体。如果使用-i参数生成还会得到soapDeviceBindingProxy.c、soapMediaBindingProxy.c等带结构性更强的代理文件。明白这些产物之间的关系后面代码写在哪、编译报错找哪个文件都心里有数了。2. 环境准备gSOAP工具的安装与ONVIF规范文件获取2.1 Linux下源码编译安装gSOAPgSOAP可以从它的官方Git仓库拉取源码编译。安装前确保系统已经安装了编译工具链和依赖库Ubuntu/Debian系和CentOS系的命令略有区别# Ubuntu / Debian sudo apt-get install -y build-essential flex bison libssl-dev libxml2-dev # CentOS / RHEL 7/8/9 sudo yum install -y gcc gcc-c make flex bison openssl-devel libxml2-devel然后从源码构建wget https://sourceforge.net/projects/gsoap2/files/gsoap-2.8.135.zip unzip gsoap-2.8.135.zip cd gsoap-2.8.135 ./configure --prefix/usr/local/gsoap make -j$(nproc) sudo make install安装完成后需要把工具目录加入系统搜索路径方便全局调用export PATH$PATH:/usr/local/gsoap/bin export GSOAP_HOME/usr/local/gsoap注意gSOAP官网SourceForge / GitHub有多个版本有些老版本比如2.7.x对ONVIF较新的扩展字段支持不完整。建议优先用2.8.1xx以上的版本实测对新版ONVIF 2.6/2.8标准的兼容性更好。2.2 ONVIF WSDL文件与依赖项的获取ONVIF官方规范文件包可以从ONVIF官网的“Documents”栏目下载或者通过其公开的规范仓库获取。打开后解压你会看到很多.wsdl和.xsd文件。实际生成代码时并不需要全部导入最关键的是这几个devicemgmt.wsdl设备管理服务media.wsdl媒体配置服务ptz.wsdl云台控制服务event.wsdl事件服务remotediscovery.wsdl设备发现服务每个WSDL文件内部会引用外部的xs:import和xs:include比如common.xsd、ver10/schema/onvif.xsd等。运行时wsdl2h会自动根据wsdl:import指定的位置去加载如果提示找不到文件就需要把这些依赖文件放到同一目录下或者用-I参数指定额外搜索路径。2.3 编写typemap.dat中文注释与类型映射调整很多教程会漏掉typemap.dat这个文件但实际踩坑之后你会发现不写好它后面会遇到各种奇奇怪怪的类型缺失和命名冲突问题。typemap.dat本身是wsdl2h工具的配置作用包括指定生成的头文件类型命名规则映射XML内置类型到自定义类型修改某些WSDL中定义不当的类型名添加自定义注释和忽略规则一个适合ONVIF C代码生成的精简typemap.dat示例# 让 xsd__duration 映射为 C 语言可用的 char* xsd__duration char* # 处理 ONVIF 中常见的引用类型缺失 tt__ReferenceToken char* # 忽略 C 环境下用不到的 C 特有类型 # 可注释掉按需调整注意如果目标是生成纯C代码不是Ctypemap.dat里如果出现C的std::string映射需要额外处理。对于C代码可以直接把字符串相关的类型映射为char*这是最简单可靠的方式。3. 核心实操流程wsdl2h soapcpp2生成C代码3.1 第一步wsdl2h生成onvif.h头文件先让环境变量就绪然后进入存放WSDL文件的目录执行命令wsdl2h -c -s -P -o onvif.h \ -I /path/to/onvif_wsdl \ -N onvif \ devicemgmt.wsdl media.wsdl ptz.wsdl event.wsdl remotediscovery.wsdl参数解读-c生成纯C代码的头文件。如果不加默认生成C头文件后续一步就无法用C编译器编译。-s去掉STL标准模板库依赖。C代码里没有STL必须加上。-P不要生成#import指令避免导入路径问题。-o onvif.h指定输出的头文件名。-I指定WSDL和XSD文件的搜索目录处理依赖关系时用。-N onvif指定命名空间前缀。生成的代码中ONVIF标准结构前缀会变成onvif__后续引用时会方便很多。执行后如果一切正常会生成一个几百KB到1MB左右的onvif.h。如果过程中报错说找不到某些xsd文件八成是依赖文件路径没弄对把它们统一放到-I指向的目录再试。3.2 第二步处理头文件中的C残留即使加了-c生成的onvif.h也偶尔会残留个别C风格的类型声明比如std::string、std::vector。这是因为ONVIF的某些WSDL定义中直接引用了xsd:anyURI或复杂类型wsdl2h在纯C模式下处理得并不完美。这时可以用sed快速做兜底替换sed -i s/std::string/char*/g onvif.h sed -i s/std::vectorchar\*//g onvif.h但这里必须提醒一句这种粗暴替换只适合“先跑通流程”的阶段。如果后续要大量使用Media的Profile结构建议在第2.3步的typemap.dat里就把xsd__string、xsd__anyURI等类型系统性地映射成char*否则生成的代码里可能出现元素名与结构体字段不一致的情况。最稳妥的方式还是多调试几轮观察头文件里哪种类型报错最多然后回到typemap.dat里去修。3.3 第三步soapcpp2生成框架C代码有了onvif.h接下来用它生成通信框架soapcpp2 -c -i -L -I /usr/local/gsoap/import \ -x -o ./generated \ onvif.h参数的含义-c生成C代码与前面的-c呼应。-i为每个服务生成独立的代理/服务文件比如soapDeviceBindingProxy.c、soapMediaBindingProxy.c便于按模块管理。-L不要同时生成没有任何服务绑定的soapClientLib.c/soapServerLib.c可选按需。-I指向gsoap自带的标准导入目录通常包含stlvector.h、soap12.h、wsdd.h等基础类型和WS-Addressing相关定义。-x不生成XML示例文件减少噪音。-o ./generated指定输出目录保持源码目录干净。执行后在./generated目录下会得到一整套C源码文件。常见的列表包括generated/ ├── soapC.c ├── soapC.h ├── soapH.h ├── soapStub.h ├── soapClient.c ├── soapServer.c ├── soapDeviceBindingProxy.c ├── soapDeviceBindingProxy.h ├── soapMediaBindingProxy.c ├── soapMediaBindingProxy.h ├── soapPTZBindingProxy.c ├── soapPTZBindingProxy.h ├── ... ... └── wsdd.nsmap其中wsdd.nsmap是命名空间表编译时需要把onvif.h里定义的命名空间和soapH.h中引用的namespace关联起来。通常stdsoap2.h/stdsoap2.cgSOAP运行库也需要一并加入工程。3.4 第四步准备Makefile并编译先准备一个最基本的Makefile把gSOAP运行库与生成的代码组合编译。一个可用的最小示例CC gcc CFLAGS -I./generated -I$(GSOAP_HOME)/include -D_DEFAULT_SOURCE LDFLAGS -L$(GSOAP_HOME)/lib -lgsoap -lpthread -lssl -lcrypto -lm OBJS main.o \ ./generated/soapC.o \ ./generated/soapClient.o \ ./generated/soapServer.o \ ./generated/soapDeviceBindingProxy.o \ ./generated/soapMediaBindingProxy.o \ ./generated/soapPTZBindingProxy.o \ ./generated/soapEventBindingProxy.o all: onvif_demo onvif_demo: $(OBJS) $(CC) -o $ $(OBJS) $(LDFLAGS) %.o: %.c $(CC) $(CFLAGS) -c $ -o $ clean: rm -f *.o onvif_demo ./generated/*.o注意main.c需要你自己写也不能漏掉那几个BindingProxy.o文件否则链接阶段会说找不到soap_call___tds__GetDeviceInformation之类的符号。4. 实际用例如何调用生成的代码获取设备信息4.1 初始化SOAP环境并调用Device服务在main.c中可以通过生成的代理模式发起一个标准的设备信息查询请求。ONVIF的设备管理服务使用DeviceBindingProxy我们有设备IP、端口、用户名、密码四个要素就可以请求GetDeviceInformation。下面是一个可以直接抄作业的实验代码#include soapH.h #include soapDeviceBindingProxy.h #include wsdd.nsmap int main(int argc, char *argv[]) { struct soap *soap; struct _tds__GetDeviceInformation req; struct _tds__GetDeviceInformationResponse resp; int result; if (argc 5) { fprintf(stderr, Usage: %s ip port user pass\n, argv[0]); return -1; } /* 创建SOAP环境允许IPv4带超时保护 */ soap soap_new2(SOAP_C_UTFSTRING, SOAP_IO_DEFAULT); soap-connect_timeout 5; soap-recv_timeout 10; soap-send_timeout 10; /* 端点地址 */ char endpoint[128]; snprintf(endpoint, sizeof(endpoint), http://%s:%s/onvif/device_service, argv[1], argv[2]); /* 填充请求GetDeviceInformation 无入参置空即可 */ memset(req, 0, sizeof(req)); /* 把用户名密码塞进SOAP头ONVIF需要带上安全信息 */ soap_wsse_add_Security(soap); soap_wsse_add_UsernameToken(soap, User, argv[3], argv[4]); DeviceBindingProxy proxy(soap); result proxy.GetDeviceInformation(endpoint, NULL, req, resp); if (result ! SOAP_OK) { soap_print_fault(soap, stderr); return -1; } printf(Manufacturer: %s\n, resp.Manufacturer); printf(Model: %s\n, resp.Model); printf(Firmware: %s\n, resp.FirmwareVersion); printf(SerialNumber: %s\n, resp.SerialNumber); /* 在C编译环境下如果生成代码使用了 char*需要手动free 如果使用C编译则需按 class 惯例处理。 */ if (resp.Manufacturer) free(resp.Manufacturer); if (resp.Model) free(resp.Model); if (resp.FirmwareVersion) free(resp.FirmwareVersion); if (resp.SerialNumber) free(resp.SerialNumber); soap_destroy(soap); soap_end(soap); soap_free(soap); return 0; }这里有个细节DeviceBindingProxy proxy(soap);是C写法但如果按前面-c参数生成的C代码在纯C文件里不能这样写需要直接调用C函数形式struct soap *soap soap_new(); struct _tds__GetDeviceInformation req; struct _tds__GetDeviceInformationResponse resp; soap_wsse_add_Security(soap); soap_wsse_add_UsernameToken(soap, User, admin, 123456); soap_call___tds__GetDeviceInformation(soap, endpoint, NULL, req, resp);如果决定用纯C开发建议干脆把工程和Makefile都按C来组织所有代码都调用soap_call__xxx形式不要在中间混用C语法。4.2 WSSE安全认证的坑ONVIF客户端调用时不少服务尤其是媒体和PTZ必须带WS-SecurityWS-SE认证否则设备会返回Sender Not Authorized错误。gSOAP自带的wsse实现需要额外引入安全模块的源码wsse.hwsse.csmdevp.hsmdevp.cmecevp.hmecevp.c这些文件通常位于gSOAP源码的gsoap/samples/wsse目录下把它们复制到工程里一起编译并在代码里调用soap_wsse_add_Security(soap); soap_wsse_add_UsernameToken(soap, User, username, password);如果不想引入这些文件也可以在第一版测试时直接调用不带鉴权的服务比如GetSystemDateAndTime验证链路走通后再补鉴权。4.3 编译运行实验效果按照上面的Makefile执行make后得到onvif_demo。针对本地或者局域网内的一台支持ONVIF的IPC跑一下./onvif_demo 192.168.1.64 80 admin admin123正常情况下会打印设备厂商、型号、固件版本和序列号。如果设备不支持这个服务或者鉴权失败会打印SOAP Fault信息。5. 常见问题与排查技巧实录从多次实操经验来看卡住大家的往往不是业务逻辑而是生成环节和编译链接期的各种小毛病。我整理了一个速查表遇到对应错误可以直接对号入座现象 / 报错可能原因解决方式wsdl2h: cant open file common.xsdWSDL依赖的XSD没找到把ONVIF依赖文件放到-I指定目录soapcpp2: error C9007: type definition ...头文件里有C残留类型sed替换std::string为char*或调整typemapundefined reference to soap_call___tds__GetDeviceInformationsoapClient.c/soapC.c没参与编译检查MakefileOBJS中是否包含soapC.o和soapClient.ofield ‘__size’ declared void类型映射冲突修改typemap.dat把数组类型映射为char**或自定义结构程序运行后段错误忘了初始化struct soap或者指针未分配soap_new()初始化环境结构体先memset(req, 0, sizeof(req))设备返回Sender Not Authorized缺少WS-Security认证或用户名密码错误引入wsse.c并调用soap_wsse_add_UsernameTokenlink: cannot find -lgsoapgSOAP库路径不对make里添加-L$(GSOAP_HOME)/lib或者直接将stdsoap2.c加入工程编译5.1 关于soapC.c和soapClient.c缺失导致的编译失败这是新手最容易踩的坑。执行完soapcpp2后默认生成的soapC.c和soapClient.c并不在项目根目录而在输出目录./generated。如果你只把onvif.h放到Makefile里忘记把soapC.o加入链接名单就会满屏的未引用符号错误。我习惯的方式是把soapC.c、soapClient.c、soapServer.c和每个服务的BindingProxy.c全部编进工程即使某些文件暂时用不到也比后面缺一个符号再改Makefile省事。5.2 纯C环境下结构体内存释放问题用-c生成的代码字符串类型基本都是char*框架内部使用malloc分配。调用完接口后如果不手动free多次查询之后内存会持续上涨。尤其在做长时间运行的设备端程序时这是一定要注意的。可以参考一个约定凡是响应结构体里的char*字段在业务逻辑用完后就free置空一来防止重复释放二来避免下次调用时残留旧数据。这对后期做内存泄漏检测比如用valgrind也是很有帮助的。5.3 命名空间与IPv6、多网卡适配ONVIF协议要求设备端网络适配做得比较细服务地址可以同时绑定IPv4/IPv6gSOAP的SOAP_IO_DEFAULT默认行为也支持双栈。生成的框架代码中需要用soap_bind的host参数来配合网卡IPONVIF Profile S要求设备服务至少支持IPv4较新的标准强调IPv6支持。实际接项目时可以先在设备上用ODMOnvif Device Manager验证基础服务可访问再在生成的代码里跑同样的调用逻辑能省掉很多联调时间。我个人在实际操作中的一个习惯是将生成后的代码固定放进一个独立的onvif/子目录不直接改动任何soap*生成文件。所有的业务逻辑都写在外部函数里通过包含soapH.h和对应的BindingProxy.h来操作。这样即使ONVIF标准升级、需要重新生成代码也不会覆盖掉已有的业务代码。再分享一个小技巧soapcpp2生成的nsmap是实现TCP/UDP 多播发现WS-Discovery的关键。如果你要写设备发现功能一定不要漏掉soap_wsdd_系列接口。它们都在wsdd.h和生成的wsdd.nsmap里有定义直接调用就行比自己构造搜索报文省不少事。这套流程我前前后后跑了不下十遍从第一次生成时的WSDL丢失到后面的鉴权问题每一步都踩实了。按这篇文章的步骤走下来第一版ONVIF框架代码基本一天内就能跑通后续无非是往回调函数里填你自己的设备逻辑。希望这篇东西能帮你少走几次弯路。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门