前文讲述了使用gsoap生成onvif代码的框架,这仅是万里长征第一步,因为这些代码是无法运行起来的,需要额外添加工作才能使用与测试工具对接。本文对这些框架代码进行一步初步的认识和学习。由于代码比较庞大,后续可能不间断地探索,慢慢积累点滴。
一、C++版本框架代码说明
下面是生成的C++版本的框架代码文件:
1 | onvif.h |
服务类实现文件形为“soapXXXBindingService”。比如,soapDeviceBindingService.cpp为设备相关服务(设备版本、IP、时间,等),soapImagingBindingService.cpp为图像调节服务(如获取、设置亮度、对比度、快门增益、白平衡,等),soapMediaBindingService.cpp为媒体服务(如H264/MPEG4,等)。云台控制的有soapPTZBindingService。
每一个服务均为一个类,继承于soap。运行于不同端口号。
下面是C版本的源码文件:
1 | onvif.h soapH.h soapServerLib.cpp wsdd.nsmap |
C版本源码文件相对很少,主体文件为soapServer.cpp。所有服务都在此文件中实现,因此端口号共用一个。
二、命名空间
在使用wsdl2h.exe工具生成onvif.h文件时,会根据gsoap源码自带的typemap.dat文件,以及用户指定的wsdl地址进行命名绑定。以下是生成的wsdd.nsmap文件内容:
1 | #include "soapH.h" |
在后面的onvif调试中可以发现,在onvif测试工具中,不同的服务类型,发送的xml内附的命名空间不同,具体可以参考生成的xml文件。
三、C++版本和C版本运行主体对比
以DeviceBindingService为例,介绍运行的主体函数。
1、C++版本
运行函数如下:
1 | int DeviceBindingService::run(int port) |
步骤:
1、用指定的端口号bind。最终调用到soap_bind函数,该函数会创建socket,然后调用setsockopt设置属性(用户可以通过修改bind_flags实现不同的功能,如地址复用),然后调用bind,然后调用listen。
2、接着调用accept接收数据,如出错,则中断退出。
3、再调用server()进行消息请求处理,如出错,中断退出
4、最后调用destroy,结束一次消息处理。destroy()调用soap_destroy、soap_end函数。
服务函数如下:
1 | int DeviceBindingService::serve() |
消息分发(处理请求)函数如下:
1 | int DeviceBindingService::dispatch() |
2、C版本
主处理函数(注:此为手动编写伪代码):
1 | while(1) |
服务函数:
1 | extern "C" SOAP_FMAC5 int SOAP_FMAC6 soap_serve(struct soap *soap) |
请求处理函数:
1 | extern "C" SOAP_FMAC5 int SOAP_FMAC6 soap_serve_request(struct soap *soap) |
注:该函数处理所有的请求,不同服务使用不同命名空间区分。
对比结论:
1、C++版本封装十分好,隐藏许多细节。C版本则相反。
2、对于不同wsdl使用不同的命名空间,和生成的onvif.h相同,C++、C版本在此方面无差异。如设备服务为命名空间为“tds”。
3、C++版本不同的服务实现为不同的类,各类有各自端口,接收处理函数为dispatch,对各属命令处理。 C版本则将其实现在同一源文件中,使用同一端口号,接收处理函数为soap_serve_request,对所有命令进行处理。
4、有的NVR只支持使用同一端口进行函数处理,无法识别不同端口。
后记: onvif在安防领域是一个很流行的标准,可以实现的功能十分强大。但要真正完成onvif,却是一个很浩大的工程。听说有些龙头企业有十几号人专门做onvif这一块,想到不久的将来,可能就我一人在开发、维护,面对未知和孤独,还是有点慌的。
李迟 2016.3.5 周六 夜