当前位置: 首页 > 流媒体学习 > 正文

onvif学习笔记5:onvif框架代码初步了解

前文讲述了使用gsoap生成onvif代码的框架,这仅是万里长征第一步,因为这些代码是无法运行起来的,需要额外添加工作才能使用与测试工具对接。本文对这些框架代码进行一步初步的认识和学习。由于代码比较庞大,后续可能不间断地探索,慢慢积累点滴。

一、C++版本框架代码说明

下面是生成的C++版本的框架代码文件:

onvif.h
soapAccessRulesBindingService.cpp
soapAccessRulesBindingService.h
soapActionEngineBindingService.cpp
soapActionEngineBindingService.h
soapAdvancedSecurityServiceBindingService.cpp
soapAdvancedSecurityServiceBindingService.h
soapAnalyticsDeviceBindingService.cpp
soapAnalyticsDeviceBindingService.h
soapC.cpp
soapCredentialBindingService.cpp
soapCredentialBindingService.h
soapDeviceBindingService.cpp
soapDeviceBindingService.h
soapDeviceIOBindingService.cpp
soapDeviceIOBindingService.h
soapDisplayBindingService.cpp
soapDisplayBindingService.h
soapDoorControlBindingService.cpp
soapDoorControlBindingService.h
soapH.h
soapImagingBindingService.cpp
soapImagingBindingService.h
soapMedia2BindingService.cpp
soapMedia2BindingService.h
soapMediaBindingService.cpp
soapMediaBindingService.h
soapPACSBindingService.cpp
soapPACSBindingService.h
soapPTZBindingService.cpp
soapPTZBindingService.h
soapPullPointSubscriptionBindingService.cpp
soapPullPointSubscriptionBindingService.h
soapReceiverBindingService.cpp
soapReceiverBindingService.h
soapRecordingBindingService.cpp
soapRecordingBindingService.h
soapRemoteDiscoveryBindingService.cpp
soapRemoteDiscoveryBindingService.h
soapReplayBindingService.cpp
soapReplayBindingService.h
soapRuleEngineBindingService.cpp
soapRuleEngineBindingService.h
soapScheduleBindingService.cpp
soapScheduleBindingService.h
soapSearchBindingService.cpp
soapSearchBindingService.h
soapStub.h
soapwsddService.cpp
soapwsddService.h
wsdd.nsmap

服务类实现文件形为“soapXXXBindingService”。比如,soapDeviceBindingService.cpp为设备相关服务(设备版本、IP、时间,等),soapImagingBindingService.cpp为图像调节服务(如获取、设置亮度、对比度、快门增益、白平衡,等),soapMediaBindingService.cpp为媒体服务(如H264/MPEG4,等)。云台控制的有soapPTZBindingService。
每一个服务均为一个类,继承于soap。运行于不同端口号。
下面是C版本的源码文件:

onvif.h    soapH.h         soapServerLib.cpp  wsdd.nsmap
soapC.cpp  soapServer.cpp  soapStub.h

C版本源码文件相对很少,主体文件为soapServer.cpp。所有服务都在此文件中实现,因此端口号共用一个。

二、命名空间

在使用wsdl2h.exe工具生成onvif.h文件时,会根据gsoap源码自带的typemap.dat文件,以及用户指定的wsdl地址进行命名绑定。以下是生成的wsdd.nsmap文件内容:

#include "soapH.h"
SOAP_NMAC struct Namespace namespaces[] = {
        {"SOAP-ENV", "http://www.w3.org/2003/05/soap-envelope", "http://schemas.xmlsoap.org/soap/envelope/", NULL},
        {"SOAP-ENC", "http://www.w3.org/2003/05/soap-encoding", "http://schemas.xmlsoap.org/soap/encoding/", NULL},
        {"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance", NULL},
        {"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema", NULL},
        {"wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing", NULL, NULL},
        {"wsdd", "http://schemas.xmlsoap.org/ws/2005/04/discovery", NULL, NULL},
        {"chan", "http://schemas.microsoft.com/ws/2005/02/duplex", NULL, NULL},
        {"wsa5", "http://www.w3.org/2005/08/addressing", "http://schemas.xmlsoap.org/ws/2004/08/addressing", NULL},
        {"c14n", "http://www.w3.org/2001/10/xml-exc-c14n#", NULL, NULL},
        {"wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", NULL, NULL},
        {"xenc", "http://www.w3.org/2001/04/xmlenc#", NULL, NULL},
        {"wsc", "http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512", NULL, NULL},
        {"ds", "http://www.w3.org/2000/09/xmldsig#", NULL, NULL},
        {"wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd", NULL},
        {"ns4", "http://www.onvif.org/ver10/pacs", NULL, NULL},
        {"xmime", "http://tempuri.org/xmime.xsd", NULL, NULL},
        {"xop", "http://www.w3.org/2004/08/xop/include", NULL, NULL},
        {"tt", "http://www.onvif.org/ver10/schema", NULL, NULL},
        {"wsrfbf", "http://docs.oasis-open.org/wsrf/bf-2", NULL, NULL},
        {"wstop", "http://docs.oasis-open.org/wsn/t-1", NULL, NULL},
        {"wsrfr", "http://docs.oasis-open.org/wsrf/r-2", NULL, NULL},
        {"ns1", "http://www.onvif.org/ver20/media/wsdl", NULL, NULL},
        {"ns2", "http://www.onvif.org/ver10/actionengine/wsdl", NULL, NULL},
        {"ns3", "http://www.onvif.org/ver10/accesscontrol/wsdl", NULL, NULL},
        {"ns5", "http://www.onvif.org/ver10/doorcontrol/wsdl", NULL, NULL},
        {"ns6", "http://www.onvif.org/ver10/advancedsecurity/wsdl", NULL, NULL},
        {"ns7", "http://www.onvif.org/ver10/accessrules/wsdl", NULL, NULL},
        {"ns8", "http://www.onvif.org/ver10/credential/wsdl", NULL, NULL},
        {"ns9", "http://www.onvif.org/ver10/schedule/wsdl", NULL, NULL},
        {"tad", "http://www.onvif.org/ver10/analyticsdevice/wsdl", NULL, NULL},
        {"tan", "http://www.onvif.org/ver20/analytics/wsdl", NULL, NULL},
        {"tdn", "http://www.onvif.org/ver10/network/wsdl", NULL, NULL},
        {"tds", "http://www.onvif.org/ver10/device/wsdl", NULL, NULL},
        {"tev", "http://www.onvif.org/ver10/events/wsdl", NULL, NULL},
        {"wsnt", "http://docs.oasis-open.org/wsn/b-2", NULL, NULL},
        {"timg", "http://www.onvif.org/ver20/imaging/wsdl", NULL, NULL},
        {"tls", "http://www.onvif.org/ver10/display/wsdl", NULL, NULL},
        {"tmd", "http://www.onvif.org/ver10/deviceIO/wsdl", NULL, NULL},
        {"tptz", "http://www.onvif.org/ver20/ptz/wsdl", NULL, NULL},
        {"trc", "http://www.onvif.org/ver10/recording/wsdl", NULL, NULL},
        {"trp", "http://www.onvif.org/ver10/replay/wsdl", NULL, NULL},
        {"trt", "http://www.onvif.org/ver10/media/wsdl", NULL, NULL},
        {"trv", "http://www.onvif.org/ver10/receiver/wsdl", NULL, NULL},
        {"tse", "http://www.onvif.org/ver10/search/wsdl", NULL, NULL},
        {NULL, NULL, NULL, NULL}
    };

在后面的onvif调试中可以发现,在onvif测试工具中,不同的服务类型,发送的xml内附的命名空间不同,具体可以参考生成的xml文件。

三、C++版本和C版本运行主体对比

以DeviceBindingService为例,介绍运行的主体函数。
1、C++版本
运行函数如下:

int DeviceBindingService::run(int port)
{     if (!soap_valid_socket(this->master) && !soap_valid_socket(this->bind(NULL, port, 100)))
       for (;;)
         return this->error;
     {     if (!soap_valid_socket(this->accept()))
          {     if (this->errnum == 0) // timeout?
                    this->error = SOAP_OK;
               break;
          }
          if (this->serve())
               break;
          this->destroy();
     }
     return this->error;
}

步骤:
1、用指定的端口号bind。最终调用到soap_bind函数,该函数会创建socket,然后调用setsockopt设置属性(用户可以通过修改bind_flags实现不同的功能,如地址复用),然后调用bind,然后调用listen。
2、接着调用accept接收数据,如出错,则中断退出。
3、再调用server()进行消息请求处理,如出错,中断退出
4、最后调用destroy,结束一次消息处理。destroy()调用soap_destroy、soap_end函数。

服务函数如下:

int DeviceBindingService::serve()
{
#ifndef WITH_FASTCGI
     unsigned int k = this->max_keep_alive;
#endif
     do
     {

#ifndef WITH_FASTCGI
          if (this->max_keep_alive > 0 && !--k)
               this->keep_alive = 0;
#endif

          if (soap_begin_serve(this))
          {     if (this->error >= SOAP_STOP)
                    continue;
               return this->error;
          }
          if (dispatch() || (this->fserveloop && this->fserveloop(this)))
          {
#ifdef WITH_FASTCGI
               soap_send_fault(this);
#else
               return soap_send_fault(this);
#endif
          }

#ifdef WITH_FASTCGI
          soap_destroy(this);
          soap_end(this);
     } while (1);
#else
     } while (this->keep_alive);
#endif
     return SOAP_OK;
}

消息分发(处理请求)函数如下:

int DeviceBindingService::dispatch()
{
     soap_peek_element(this);
     if (!soap_match_tag(this, this->tag, "tds:GetServices"))
          return serve___tds__GetServices(this);
     if (!soap_match_tag(this, this->tag, "tds:GetServiceCapabilities"))
          return serve___tds__GetServiceCapabilities(this);
     if (!soap_match_tag(this, this->tag, "tds:GetDeviceInformation"))
          return serve___tds__GetDeviceInformation(this);
     if (!soap_match_tag(this, this->tag, "tds:SetSystemDateAndTime"))
          return serve___tds__SetSystemDateAndTime(this);
     if (!soap_match_tag(this, this->tag, "tds:GetSystemDateAndTime"))
          return serve___tds__GetSystemDateAndTime(this);
    // ...
     return this->error = SOAP_NO_METHOD;
}

2、C版本
主处理函数(注:此为手动编写伪代码):

while(1)
    {
        //pthread_mutex_lock(&mutex);

        s = soap_accept(add_soap);
        if (s < 0)
        {
            soap_print_fault(add_soap, stderr);
            onvif_debug(3, "Socket connection faild\n");
        }
        else
        {
            onvif_debug(3, "soap_accept from %s:%d\n", inet_ntoa(add_soap->peer.sin_addr), ntohs(add_soap->peer.sin_port));
        }
        onvif_debug(6, "--socket = %d, accepting the %d time\n", s, i++);
        soap_serve(add_soap);
        soap_end(add_soap);

        onvif_debug(6, "--closing socket %d\n", s);
        close(s);
        sleep(1);

        //pthread_mutex_unlock(&mutex);
    }

服务函数:

extern "C" SOAP_FMAC5 int SOAP_FMAC6 soap_serve(struct soap *soap)
{
#ifndef WITH_FASTCGI
     unsigned int k = soap->max_keep_alive;
#endif
     do
     {
#ifndef WITH_FASTCGI
          if (soap->max_keep_alive > 0 && !--k)
               soap->keep_alive = 0;
#endif
          if (soap_begin_serve(soap))
          {     if (soap->error >= SOAP_STOP)
                    continue;
               return soap->error;
          }
          if (soap_serve_request(soap) || (soap->fserveloop && soap->fserveloop(soap)))
          {
#ifdef WITH_FASTCGI
               soap_send_fault(soap);
#else
               return soap_send_fault(soap);
#endif
          }

#ifdef WITH_FASTCGI
          soap_destroy(soap);
          soap_end(soap);
     } while (1);
#else
     } while (soap->keep_alive);
#endif
     return SOAP_OK;
}

请求处理函数:

extern "C" SOAP_FMAC5 int SOAP_FMAC6 soap_serve_request(struct soap *soap)
{
     soap_peek_element(soap);
     if (!soap_match_tag(soap, soap->tag, "SOAP-ENV:Fault"))
          return soap_serve_SOAP_ENV__Fault(soap);
     // ...
     if (!soap_match_tag(soap, soap->tag, "tds:GetServiceCapabilities"))
          return soap_serve___tds__GetServiceCapabilities(soap);
     if (!soap_match_tag(soap, soap->tag, "tds:GetDeviceInformation"))
          return soap_serve___tds__GetDeviceInformation(soap);
     if (!soap_match_tag(soap, soap->tag, "tds:SetSystemDateAndTime"))
          return soap_serve___tds__SetSystemDateAndTime(soap);
     if (!soap_match_tag(soap, soap->tag, "tds:GetSystemDateAndTime"))
          return soap_serve___tds__GetSystemDateAndTime(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 周六 夜

本文固定链接: http://www.latelee.org/multistream/onvif-note5-onvif-framework.html

如无特别说明,迟思堂工作室文章均为原创,转载请注明: onvif学习笔记5:onvif框架代码初步了解 | 迟思堂工作室

目前暂无评论

发表评论

*

快捷键:Ctrl+Enter