ONVIF协议开发实战指导

来源:互联网 发布:如果有来生 知乎 编辑:程序博客网 时间:2024/06/07 17:28

一、ONVIF简介

只需要知道ONVIF就是AXIS、SONY、BOSH等公司领导制定的一套安防行业设备通用的网络接入协议规范,便于不同厂商的安防设备能够互联集成,改变各自为政的混乱局面,就可以啦。

ONVIF就是Open Network Video Interface Forum 的简称。

 

二、ONVIF开发

1、开发简介

ONVIF是基于WebSevices的一个协议规范,参考了很多现有的协议标准,比如说设备发现WS-discovery等。

 

ONVIF开发简单来说:就是ONVIF官网提供了协议内容描述文件,即WSDL文档,能用来生成安防设备间网络通讯的一个接口框架,包括客户端和服务端的框架代码,我们只需要在生成的框架基础上添砖加瓦就可以了,这样客户端可以通过这个接口调用远端服务,服务端可以提供相应的服务实现,中间的网络通讯细节可以不用考虑,节省了各家厂商的协议开发时间,然后就ok了。

 

ONVIF官方还提供了测试工具,可以用来检验我们开发的协议是否符合标准,最新的测试工具同时支持黑盒和白盒测试。

 

2、协议开发流程:

(1)协议规范下载

首先到官网下载协议规范specification和一系列的.WSDL以及.XSD文件

 

ONVIF 官网

http://www.onvif.org/

 

官网的协议规范下载地址:

http://www.onvif.org/Documents/Specifications.aspx

其中主要是以下两个:

ONVIF Core Specification Version xxx ReleaseNotes

ONVIF WSDL and XML Schemas Specifications

 

对于WSDL、XSD等文件点击链接打开,然后在浏览器中文件保存到本地就可以了,下载完之后大概如下图所示:

 

上图一些在官网没找到的文件,自己去百度搜啦,因为ONVIF是基于现有一些标准协议做的,所以文档有一些也是沿用。

 

(2)使用Gsoap生成协议框架

 

gsoap是用来解析WSDL等文档,然后生成协议框架用的一个软件,自行搜之。

 

gsoap用法

gsoap包括两个工具:wsdl2h.exe和soapcpp2.exe

wsdl2h先预生成一个xxx.h文件(如onvif.h),然后再用soapcpp2处理这个xxx.h去生成.c/c++文件,也就是协议框架。

 

其中:wsdl2h 将从官网下载下来的.wsdl等文件转换成一种特殊的.h文件,这种.h文件不同于一般的.h文件,它定义了客户端和服务的所有框架,其中的//注释后面的内容也是有用途的,不是随便编写的。但生成的.h文件的名字是可以任意的。这里我们使用onvif.h。

soapcpp2工具将前面个生成的.h文件转换为一系列文件,包括.h.c .nsmap .xml 等文件。

 

gsoap使用示例

在windows下写两个.bat文件,先后执行gen-h.bat和gen-c.bat来进行生成框架接口。

不一定非要把所有的wsdl一起来转换,如果只是练习,拿一个wsdl就可以。

gen-h.bat :

rem

wsdl2h.exe -sck -t WS-typemap.dat -oonvif.h analytics.wsdl analyticsdevice.wsdl devicemgmt.wsdl display.wsdlevent.wsdl imaging.wsdl media.wsdl ptz.wsdl receiver.wsdl recording.wsdlremotediscovery.wsdl replay.wsdl search.wsdl deviceio.wsdl

rem wsdl2h.exe -sck -tK:\OpenSource\tools\gSOAP\gsoap-2.8\gsoap\WS\WS-typemap.dat -o onvif.hanalytics.wsdl analyticsdevice.wsdl deviceio.wsdl devicemgmt.wsdl display.wsdlevent.wsdl imaging.wsdl media.wsdl ptz.wsdl receiver.wsdl recording.wsdlremotediscovery.wsdl replay.wsdl search.wsdl

rem wsdl2h.exe -sck -t WS-typemap.dat -oonvif.h remotediscovery.wsdl

gen-c.bat :

soapcpp2.exe -2 -L -c onvif.h -d..\-IK:\OpenSource\tools\gSOAP\gsoap-2.8\gsoap\import

cd ..

mkdir xml

mv *.xml xml

 

mkdir nsmap

mv *.nsmap nsmap

cp nsmap/RemoteDiscoveryBinding.nsmaponvif.nsmap

 

(3)实现接口函数

我们这里主要用到gsoap生成的soapStub.h soapH.h soapC.c soapClient.c soapServer.c 以及.nsmap文件,除此只为还要从gsoap工具安装目录下找到stdsoap2.hstdsoap2.c文件添加到工程文件夹下。

 

以上文件准备好之后就可以自己编写客户端(nvc) 和服务端(nvt)程序了。

 

其中soapStub.h比较重要,里面包含了客户端和服务端的接口函数声明,我们的工作主要就是实现这些函数的定义,不用考虑传输过程,这就是gsoap wsdl带来的好处。

 

以下就是soapStub.h当中关于客户端和服务端的接口声明函数标识:

/******************************************************************************\ *                                                                           * * Server-SideOperations                                                    * *                                                                           *\******************************************************************************/SOAP_FMAC5 int SOAP_FMAC6__daae__GetServiceCapabilities(struct soap*, struct_tan__GetServiceCapabilities *tan__GetServiceCapabilities, struct_tan__GetServiceCapabilitiesResponse *tan__GetServiceCapabilitiesResponse);…./******************************************************************************\ *                                                                           * * Client-SideCall Stubs                                                    * *                                                                            *\******************************************************************************/SOAP_FMAC5 int SOAP_FMAC6soap_call___daae__GetServiceCapabilities(struct soap *soap, const char*soap_endpoint, const char *soap_action, struct _tan__GetServiceCapabilities*tan__GetServiceCapabilities, struct _tan__GetServiceCapabilitiesResponse*tan__GetServiceCapabilitiesResponse);….


三、代码示例(Discovery)

设备发现服务中规定,设备上线要先发组播hello。

服务端NVT的DISCOVERY中部分代码如下:

/*-****************************************************************-*/*启动设备发现线程/*-****************************************************************-*/voidStart_Onvif_Discovery( void ){/* initialize the soap structure */   Soap_nvt_Init(&soap1); /* multicast a Hello message to the multicast group239.255.255.250*/  multicast_Hello(&soap1); /* bind local soap port *//* add the nvt soap-socket into the multicast group toreceive the request of Remote Probe for Device */  multicast_Probe_Init(&soap1); /* get into the "soap_serve()" loop to waitfor the remote request andinvolde the relevant function defined in the xxx_nvt.c,eg. discovery_nvt.c */   onvif_thread_t discovery_t;     if(INF_Onvif_THCreate_API( &discovery_t, NULL, (void *)INF_Onvif_Discovery_TH,NULL ) )    {       perror("create discovery server error");        return ;    }}/*-****************************************************************-*/intSoap_nvt_Init(struct soap *soap){/* initialize the soap structure */    soap_init2(soap, SOAP_IO_UDP|SOAP_IO_FLUSH, SOAP_IO_UDP|SOAP_IO_FLUSH);   //soap_set_namespaces(soap,namespaces);return SOAP_OK;}/*-****************************************************************-*/*NVT上线组播发HELLO/*-****************************************************************-*/ int multicast_Hello(struct soap *soap){        staticstruct d__HelloType req;    static structd__ScopesType xScope;        staticstruct SOAP_ENV__Header header;        staticchar s_XAddr[64];         charguid_string[100];        uuid_tuuid;           uuid_generate(uuid);       strcpy(guid_string, "urn:uuid:");       uuid_unparse(uuid, guid_string+9);     header.wsa5__MessageID= guid_string;    header.wsa5__To= "urn:schemas-xmlsoap-org:ws:2005:04:discovery";    header.wsa5__Action= "http://schemas.xmlsoap.org/ws/2005/04/discovery/Hello";    soap->header= &header;     soap_default_d__ScopesType(soap,&xScope);    xScope.__item= "onvif://www.onvif.org/type/Network_Video_Transmitter onvif://www.onvif.org/hardware/ARM&DSP  onvif://www.onvif.org/location/country/Chinaonvif://www.onvif.org/location/commpany/Sony onvif://www.onvif.org/name/E-NVR onvif://www.onvif.org/name/hibox";     soap_default_d__HelloType(soap,&req);    req.Scopes =&xScope;    req.Types ="dn:NetworkVideoTransmitter";         staticchar s_EndpointReference[64];       sprintf(s_EndpointReference,"URN:%s","uuid:464A4854-4656-5242-4530-313035394100");        //getLocalIP(soap);       strcpy(localIP,"192.168.123.183");       sprintf(s_XAddr, "http://%s/onvif/device_service", localIP);    req.XAddrs =s_XAddr;//"http://192.168.7.98/onvif/device_service";       req.wsa__EndpointReference.Address= s_EndpointReference;    req.MetadataVersion= 1;         soap_call_Hello(soap,END_POINT,NULL,&req);    returnSOAP_OK;}/*-****************************************************************-*/*本函数的实现是参考soapClient.c里面的函数实现,比较特殊,因为只有这个是nvt主动发给nvc/*-****************************************************************-*/int soap_call_Hello(struct soap *soap, const char*soap_endpoint, const char *soap_action, struct d__HelloType *d__Hello){    struct__dnrd__Hello soap_tmp___dnrd__Hello;    if(!soap_action)        soap_action= "http://www.onvif.org/ver10/network/wsdl/Hello";    soap->encodingStyle= NULL;    soap_tmp___dnrd__Hello.d__Hello= d__Hello;    soap_begin(soap);    soap_serializeheader(soap);    soap_serialize___dnrd__Hello(soap,&soap_tmp___dnrd__Hello);    if(soap_begin_count(soap))        returnsoap->error;    if(soap->mode & SOAP_IO_LENGTH)    {   if (soap_envelope_begin_out(soap)         || soap_putheader(soap)         || soap_body_begin_out(soap)         || soap_put___dnrd__Hello(soap,&soap_tmp___dnrd__Hello, "-dnrd:Hello", NULL)         || soap_body_end_out(soap)         || soap_envelope_end_out(soap))             return soap->error;    }    if(soap_end_count(soap))        returnsoap->error;    if(soap_connect(soap, soap_endpoint, soap_action)     || soap_envelope_begin_out(soap)     || soap_putheader(soap)     || soap_body_begin_out(soap)     || soap_put___dnrd__Hello(soap,&soap_tmp___dnrd__Hello, "-dnrd:Hello", NULL)     || soap_body_end_out(soap)     || soap_envelope_end_out(soap)     || soap_end_send(soap))        returnsoap_closesock(soap); return SOAP_OK;}/*-****************************************************************-*/*NVT响应NVC设备探测*服务端大部分都是像probe这种形式的函数,NVC调用NVT,但hello是例外,方向相反。/*-****************************************************************-*/SOAP_FMAC5 int SOAP_FMAC6 __dndl__Probe(struct soap*soap, struct d__ProbeType *d__Probe, struct d__ProbeMatchesType*d__ProbeMatches){    staticstruct d__ProbeMatchType s_ProbeMatch;    staticstruct d__ScopesType sz_scopes;    static chars_MessageID[100];    static chars_XAddr[64];    static chars_EndpointReference[64];    staticstruct wsa5__RelatesToType s_wsa_RelatesTo;    getLocalIP(soap);       if(soap->header)    {           uuid_t uuid;                uuid_generate(uuid);           strcpy(s_MessageID, "uuid:");           uuid_unparse(uuid, s_MessageID+5);         if(soap->header->wsa5__MessageID)        {            soap->header->wsa5__RelatesTo=&s_wsa_RelatesTo;            soap_default__wsa5__RelatesTo(soap,soap->header->wsa5__RelatesTo);           soap->header->wsa5__RelatesTo->__item =soap->header->wsa5__MessageID;              (soap->header->wsa5__MessageID) =s_MessageID;            soap->header->wsa5__To ="http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous";            soap->header->wsa5__Action ="http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches";        }    }        sprintf(s_XAddr,"http://%s:%d/onvif/device_service",localIP,PORT2);       //sprintf(s_EndpointReference, "urn:%s",s_MessageID);         soap_default_d__ProbeMatchType(soap,&s_ProbeMatch);       soap_default_d__ScopesType(soap, &sz_scopes);      sz_scopes.__item= "onvif://www.onvif.org/type/Network_Video_Transmitter"; /*onvif://www.onvif.org/hardware/ARM&DSP  onvif://www.onvif.org/location/country/Chinaonvif://www.onvif.org/location/commpany/SONY onvif://www.onvif.org/name/E-NVRonvif://www.onvif.org/name/hibox";*/     s_ProbeMatch.wsa__EndpointReference.Address= s_EndpointReference;   sprintf(s_ProbeMatch.wsa__EndpointReference.Address,"urn:%s",s_MessageID); /*       if(!strcmp(d__Probe->Types,"dn:NetworkVideoTransmitter"))        s_ProbeMatch.Types ="dn:NetworkVideoTransmitter";                //device named wsrf_rw will befound by the new test tools        else*/    s_ProbeMatch.Types ="dn:NetworkVideoTransmitter";                 s_ProbeMatch.Scopes= &sz_scopes;    s_ProbeMatch.XAddrs= s_XAddr;                               //"http://192.168.7.98/onvif/device_service";    s_ProbeMatch.MetadataVersion= 1;    d__ProbeMatches->__sizeProbeMatch = 1;    d__ProbeMatches->ProbeMatch= &s_ProbeMatch;     returnSOAP_OK; }


四、结语

好了,有了以上的理解以及附录的提示,就应该可以开始ONVIF的开发了,主要的工作还是完成生成的接口的实现。

附录1

Gsoap注意事项:

1、remotedicovery.wsdl 文件引用的schemalocation="ws-discovery.xsd"。其中schemalocation是当前.wsdl文件引用的.xsd文件的路径。具体可以根据相应的.xsd文件的实际位置确定。

2、引用onvif.xsd是不对的。onvif.xsd没有对remotediscovery.wsdl的类型规范。

3、其他的例如:devicemgmnt.wsdl引用的是schemalocaiton="onvif.xsd"文件。

4、网上有一些typemap.dat文件是错误的,会导致connecting..xop/include错误。网上自行找到正确的typemap.dat文件。

5、生成xxx.h (例如onvif.h)后,打开该文件增加 #import"wsse.h" 这一行

6、 使用wsdl2h的时候把deviceio.wsdl放在devicemgmt.wsdl和media.wsdl后面,因为deviceio.wsdl文件import这两个文件,如果不放在后面,会导致devicemgmt.wsdl和media.wsdl的相关功能函数缺失

附录2

Onvif 之Web Services

术语“Web Services”是一个标准方法的名称,该方法集成了使用开放的、独立平台的Web Services 标准的众多应用程序,这些Web Services标准包括XML、SOAP1.2[Part 1]和基于IP网络的WSDL 1.1等。XML被用作数据描述语法,SOAP被用作消息传递,WSDL被用作描述服务。

 

本框架是建立在Web Services标准之上的。在该标准当中定义的所有配置服务都将表示成Web Services操作(operations),并且用WSDL来详细定义,该WSDL是以HTTP作为下面的传输机制的。

Figure1给出了基于Web Services的开发基本原理的概要。服务提供者(device)执行ONVIF某个服务或者一系列服务。这些服务用基于XML的WSDL描述语言描述出来。然后,WSDL被用作服务请求者(client)的执行/集成的基础。客户端的综合通过使用WSDL编译工具得到了简化,WSDL编译工具用于生成特定平台的代码,这些代码可以被客户端开发者用于把Web Services集成到一个应用程序中去。


Web Services提供方和请求方使用SOAP消息交换协议来进行交流。SOAP是一个轻量级的,基于XML的消息协议,该协议被用于在通过网络发送它们之前,编码Web Services的请求消息和应答消息当中的信息。SOAP消息不依赖于任何操作系统或协议,并且可以用多种Internet协议传输。本ONVIF规范定义相应的用于SOAP消息的传输协议,该SOAP消息用于描述Web Services。

 

Web Service概述模块定义了不同的ONVIF服务,规范当中的命令定义语法,错误处理法则和采用的Web Services安全机制。

 

为了保证互操作性,所有定义的服务都要遵守网络服务可互操作组织(Web Services Interoperability Organization(WS-I))基本概要2.0版的建议,并且使用文档或者字面预包装模式。

附录3

名词解释

Endpoint

服务端点 
也就相当于一个url服务, 
比如http://www.xxx.com/service.action之类

附录4  soap_serve函数解析

下面是网上分享的一个关于soap_serve()函数的分析

 

soap_serve(struct soap *soap)分析——gSOAP是要分服务分解报文的  


SOAP_FMAC5 int SOAP_FMAC6 soap_serve(structsoap *soap)分析
1 调用SOAP_FMAC1 void SOAP_FMAC2 soap_begin(struct soap *soap)初始化
  初始化soap的成员变量,并释放可能存在的内存占用。

2 调用SOAP_FMAC1 intSOAP_FMAC2 soap_begin_recv(struct soap *soap)准备接收
  实际上在这里就完成了报文的接收,内部调用了下面的函数:
  # define soap_get1(soap) (((soap)->bufidx>=(soap)->buflen&& soap_recv(soap)) ? EOF : (unsignedchar)(soap)->buf[(soap)->bufidx++])
  该函数将报文全部存入缓冲区soap->buf,bufidx标明报文长度,buflen标明buf缓冲区长度
 
3 调用SOAP_FMAC1 int SOAP_FMAC2 soap_envelope_begin_in(struct soap *soap)
      SOAP_FMAC1 int SOAP_FMAC2soap_recv_header(struct soap *soap)
  将SOAP头部信息解析出来
 
4 调用SOAP_FMAC5 int SOAP_FMAC6 soap_serve_request(struct soap *soap)
      SOAP_FMAC1 int SOAP_FMAC2soap_body_begin_in(struct soap *soap)
  接收SOAP报文体
  针对不同的服务,进行特殊处理。
  SOAP_FMAC5 int SOAP_FMAC6 soap_serve_request(struct soap *soap)
 {
  soap_peek_element(soap);
  if (!soap_match_tag(soap, soap->tag,"NS:CustInfoRequest"))
   return soap_serve_NS__CustInfoRequest(soap);
  if (!soap_match_tag(soap, soap->tag,"NS:BalanceRequest"))
   return soap_serve_NS__BalanceRequest(soap);
  if (!soap_match_tag(soap, soap->tag,"NS:QueryBalanceRequest"))
   return soap_serve_NS__QueryBalanceRequest(soap);
  if (!soap_match_tag(soap, soap->tag,"NS:QueryAcctRequest"))
   return soap_serve_NS__QueryAcctRequest(soap);
  if (!soap_match_tag(soap, soap->tag,"NS:QueryResourcesRequest"))
   return soap_serve_NS__QueryResourcesRequest(soap);
  return soap->error = SOAP_NO_METHOD;
 }
 关键部分!看到这里之前我猜想该有这么一种处理机制。

 

0 0
原创粉丝点击