2.onvif笔记 --实现Probe命令来进行设备发现(discover)

ONVIF协议很复杂,我们需要借助工具来实现代码框架。这个工具叫做gSOAP。soap协议进行封装为c/c++代码,这样我们就不需要关心soap协议,只需要考虑逻辑层就可以了。接下来我们就开始搭建环境

1.编译gSOAP

版本为2.8.15
我们来用的是2.8.94,结果很多字段设备不支持,就用2.8.15
要使用工具,我们就得线编译工具,

  • 下载gSOAP2.8
  • 上传到Linux,在linux上编译,我不管你怎么上传,哪怕用意念
  • 解压 :unzip gsoap_2.8.15.zip
  • 配置:./configure --prefix=/home/wxf/work/software/gsoap-2.8
    需要说明的是,prefix是指明安装路径
  • 编译:make
  • 安装:make install
    说明
    我的安装路径是/home/wxf/work/software/gsoap-2.8/在编译成功后,在该路径下会生成如下目录
    在这里插入图片描述
    在bin目录里有如下两个工具
  • wsdl2h:将wsdl协议文件生成头文件 ./wsdl2h -h 查看帮助
  • soapcpp2:根据上面的头文件生成源文件C/C++代码

2.生成头文件

onvif代码都是靠工具生成的,我们得根据我们的要求生成头文件,onvif是根据模块来生成头文件,需要的模块多,生成的代码就庞大,相反则很少,我们接下来使用onvif官方提供的wsdl来生成代码
我们可以先建立一个目录,当作我们的工程目录名字随意,这里我们要实现的功能是设备发现,所以叫做onvif-discover
该目录下有如下目录

  • bin:放置上面生成的两个工具,注意要加可执行权限 chmod +x wsdl2h soapcpp2
  • gsoap:上面编译后的gsoap文件夹,其路径为 /home/wxf/work/software/gsoap-2.8/share ,此外,我们还需要将gsoap源代码下的几个文件考到我们的gsoap目录下,dom.c stdsoap2.c stdsoap2.h 我也不知道为啥,靠过来就是了
    目录创建好了,我们就可以生成头文件了
    可以直接执行命令,我觉得编解脚本比较方便,脚本名字就叫onvif_head.sh 内容如下
#!/bin/bash
mkdir onvif_head
cd onvif_head
../bin/wsdl2h -o onvif.h -c -s -t ../gsoap/WS/typemap.dat \
http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl

接下来解释一下

  • -o onvif.h:为生成onvif.h头文件
  • -c:生成C代码
  • -s:不生产STL
  • -t:指定使用的dat文件,就是当前目录的gosap/WS目录下
  • http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl 为 remotediscovery的wsdl文件,我们这里需要的功能不多,用不到很多的的wsdl文件,所以直接就官网链接,如果多了的话,就下载下来,因为官网速度太慢

好了,脚本编写好了,我们就需要把他上传到linux上执行,需要先添加可执行权限,逆行后你会发现,出错
在这里插入图片描述
这是因为在windos下和linux下的文件不兼容的原因,解决方法
这个时候就生成了一个onvif_head的目录,里面包含我们的onvif.h文件

3.生成源代码

我们既然有了头文件,接下来还需要根据这个头文件生成源文件,需要使用soapcpp2工具。通用,这里还是选择编写脚本gen_code.sh

#!/bin/bash
DIR=soap
mkdir $DIR
cd $DIR
 ../bin/soapcpp2 -2 -c ../onvif_head/onvif.h  -L -I ../gsoap/import -I ../gsoap/

解释一下

  • -2:生成SOAP 1.2版本代码
  • -c:生成C代码
  • -L:不生成soapClientLib/soapServerLib文件,这里的代码比较简单,我们就使用这几个代码,也是为了简单方便,不过之后正式的工程为了规范我觉得还是使用比较好
  • -H:指定头文件目录

上传到linux执行就可以生成源代码了
注意
如果报将wsa5.h的SOAP_ENV__Fault重复定义的错,只需将wsa5.h的SOAP_ENV__Fault改个名字就好,比如在结尾加个1。
生成的文件如下

  • 各种xml文件:一个xml文件就是一个soap消息
  • 各种nsmap文件:命名空间,其实他们除了名字不一样,内容是一样的,细心的可以观察一下,里面的内容竟然是每一个xml文件里的Envelope字段内容
  • soapC.c:指定数据结构的序列化和反序列化
  • soapClient.c:客户端代码
  • soapH.h:主头文件,所有客户机和服务器源代码都要包括他
  • soapServer.c:服务器端代码
  • soapStub.h:从输入头文件(onvif.h)生成的经过修改且带命名空间前缀的头文件

顺道说明一下

  • stdsoap2.c/cpp文件时运行时的c/c++库,带HTTP/SOAP解析器和运行时支持历程。通过这两个文件和上述的框架代码,就可以开发客户端/服务器端代码了

4.组织工程框架

我们需要的文件都齐活了,接下来组织工程框架,现在我们的需要的代码并不多,并且现在是实例代码,为了方便,我们将所有需要的代码放到同一个目录中。我们在刚才建立的onvif-discover目录下创建一个application目录,存放我们需要的所有代码
。我们需要将一下代码拷贝到application目录下
在这里插入图片描述
一些代码是在gsoap下的某一个目录下,自己找找就找到了,一些是我们刚才生成的。
到这里,然后在该目录中添加一个空的deivceserver.c、deviceprobe.c和一个空的Makefile。代码框架就完成了,为了方便,我们使用source insight来创建工程

5.编写代码

完成上面的流程后,就可以编写代码了

5.1 服务端代码

如下内容在deivceserver.c中实现

首先我们需要实现几个函数定义
可以自己找找这几个函数是在哪里声明的

SOAP_FMAC5 int SOAP_FMAC6 __tdn__Hello(struct soap* soap, struct wsdd__HelloType tdn__Hello, struct wsdd__ResolveType *tdn__HelloResponse)
{
	printf("__tdn__Hello %s, %d\n", __FUNCTION__, __LINE__);

	return 0;
}
/** Web service operation '__tdn__Bye' implementation, should return SOAP_OK or error code */
SOAP_FMAC5 int SOAP_FMAC6 __tdn__Bye(struct soap* soap, struct wsdd__ByeType tdn__Bye, struct wsdd__ResolveType *tdn__ByeResponse)
{
	printf("__tdn__Bye %s, %d\n", __FUNCTION__, __LINE__);
	
	return 0;
}
/** Web service operation '__tdn__Probe' implementation, should return SOAP_OK or error code */
SOAP_FMAC5 int SOAP_FMAC6 __tdn__Probe(struct soap* soap, struct wsdd__ProbeType tdn__Probe, struct wsdd__ProbeMatchesType *tdn__ProbeResponse)
{
	printf("__tdn__Bye %s, %d\n", __FUNCTION__, __LINE__);
	
	return 0;
}

/** Web service one-way operation 'SOAP_ENV__Fault' implementation, should return value of soap_send_empty_response() to send HTTP Accept acknowledgment, or return an error code, or return SOAP_OK to immediately return without sending an HTTP response message */
SOAP_FMAC5 int SOAP_FMAC6 SOAP_ENV__Fault(struct soap*soap, char *faultcode, char *faultstring, 
												char *faultactor, struct SOAP_ENV__Detail *detail, 
												struct SOAP_ENV__Code *SOAP_ENV__Code, 
												struct SOAP_ENV__Reason *SOAP_ENV__Reason, 
												char *SOAP_ENV__Node, char *SOAP_ENV__Role, 
												struct SOAP_ENV__Detail *SOAP_ENV__Detail)
{
	printf("SOAP_ENV__Fault %s, %d\n", __FUNCTION__, __LINE__);

	return 0;
}

void wsdd_event_Hello(struct soap *soap, unsigned int InstanceId, const char *SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, const char *EndpointReference, const char *Types, const char *Scopes, const char *MatchBy, const char *XAddrs, unsigned int MetadataVersion)
{
	printf("wsdd_event_Hello %s, %d\n", __FUNCTION__, __LINE__);

}

void wsdd_event_Bye(struct soap *soap, unsigned int InstanceId, const char *SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, const char *EndpointReference, const char *Types, const char *Scopes, const char *MatchBy, const char *XAddrs, unsigned int *MetadataVersion)
{
	printf("wsdd_event_Bye %s, %d\n", __FUNCTION__, __LINE__);
	
}


soap_wsdd_mode wsdd_event_Probe(struct soap *soap, const char *MessageID, const char *ReplyTo, const char *Types, const char *Scopes, const char *MatchBy, struct wsdd__ProbeMatchesType *matches)
{
	printf("%s,%d\n",__FUNCTION__, __LINE__);
	printf("MessageID:%s\n", MessageID);
	printf("ReplyTo:%s\n",ReplyTo);
	printf("Types:%s\n",Types);
	printf("Scopes:%s\n",Scopes);
	printf("MatchBy:%s\n",MatchBy); 


	soap_wsdd_init_ProbeMatches(soap, matches);
	soap_wsdd_add_ProbeMatch(soap, matches, 
								 "urn:uuid:464A4854-4656-5242-4530-313035394100",
								 "ns1:NetworkVideoTransmitter",
								 "onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/type/audio_encoder onvif://www.onvif.org/hardware/IPC-model onvif://www.onvif.org/name/IPC-model",
          						 NULL, 
								 "http://10.0.0.47/onvif/device_service", 10);


	return 0;
}

void wsdd_event_ProbeMatches(struct soap * soap, unsigned int InstanceId, const char * SequenceId, unsigned int MessageNumber, const char * MessageID, const char * RelatesTo, struct wsdd__ProbeMatchesType * matches)
{
	printf("wsdd_event_ProbeMatches %s, %d\n", __FUNCTION__, __LINE__);
	
}


soap_wsdd_mode wsdd_event_Resolve(struct soap * soap, const char * MessageID, const char * ReplyTo, const char * EndpointReference, struct wsdd__ResolveMatchType * match)
{
	printf("wsdd_event_Resolve %s, %d\n", __FUNCTION__, __LINE__);
	
}

void wsdd_event_ResolveMatches(struct soap * soap, unsigned int InstanceId, const char * SequenceId, unsigned int MessageNumber, const char * MessageID, const char * RelatesTo, struct wsdd__ResolveMatchType * match)
{
	printf("wsdd_event_ResolveMatches %s, %d\n", __FUNCTION__, __LINE__);
	
}

主要来讲讲wsdd_event_Probe函数,这个函数是服务端在接收到client端的probe消息后,server端调用该函数来回复client消息
他的调用过程如下

  • main:主函数
  • soap_serve:阻塞,等待接收client的probe消息
  • soap_serve___wsdd__Probe:解析收到的消息类型
  • __wsdd__Probe:判断是probe消息
  • wsdd_event_Probe:回复client
  • soap_wsdd_add_ProbeMatch:组装回复消息

接下来是main函数

#include "soapStub.h"
#include "wsdd.nsmap"
#include "wsddapi.h"

int main(int argc, char **argv)
{
	int m, s;
	struct ip_mreq mcast;

	struct soap probe_soap;
	//指定创建UDP
	soap_init2(&probe_soap, SOAP_IO_UDP|SOAP_IO_FLUSH, SOAP_IO_UDP|SOAP_IO_FLUSH);
	/* reuse socket addr */
	probe_soap.bind_flags = SO_REUSEADDR;
	
	soap_set_namespaces(&probe_soap, namespaces);

	if(!soap_valid_socket(soap_bind(&probe_soap, NULL, 3702, 10)))
	{
		soap_print_fault(&probe_soap, stderr);
		exit(1);
	}

	//多播组
	mcast.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
	//加入的客户端主机IP,这里设置为INADDR_ANY表示所有ip
	mcast.imr_interface.s_addr = htonl(INADDR_ANY);

	if(setsockopt(probe_soap.master, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast)) < 0)
	{
		printf("setsockopt error!\n");

		return 0;
	}

	//成功绑定后,开始鉴定
	for(;;)
	{
		if(!soap_valid_socket(soap_accept(&probe_soap)))
		{
			soap_print_fault(&probe_soap, stderr);

			exit(-1);
		}

		fprintf(stderr, "Socket connection successful:slave socket = %d \n", s);

		/* 连接成功后,便开始处理响应请求,下面的函数就是用来处理并响应请求
			封装了所有的处理与响应过程,在该函数中调用了本地所实现的相关web服务
			他所需要的所有请求信息都找soap结构体中
		*/
		if (soap_serve(&probe_soap))
      		soap_print_fault(&probe_soap, stderr);
		soap_destroy(&probe_soap);	//处理完后,撤销soap环境
		soap_end(&probe_soap);		//清楚所有资源,关闭套接字
	}

	soap_done(&probe_soap);

	return 0;
}

5.2 客户端代码

对于客户端代码的各个字段含义,参看下面的xml文件

#include "soapStub.h"
#include "wsdd.nsmap"
#include "soapH.h"

//#include "uuid32.h"

int main(int argc, char *argv[])
{
	//soap环境变量
	struct soap *soap;

	//发送消息描述
	struct wsdd__ProbeType req;
	struct wsdd__ProbeType wsdd__Probe;

	struct __wsdd__ProbeMatches resp;

	//描述查找那类的Web消息
	struct wsdd__ScopesType sScope;

	//soap消息头消息
	struct SOAP_ENV__Header  header;

	//获得的设备信息个数
	int count = 0;

	//返回值
	int result = 0;

	//存放uuid 格式(8-4-4-4-12)
	char uuid_string[64];

	printf("%s: %d 000: \n", __FUNCTION__, __LINE__);
	sprintf(uuid_string, "464A4854-4656-5242-4530-110000000000");
	printf("uuid = %s \n", uuid_string);

	//soap初始化,申请空间
	soap = soap_new();
	if(soap == NULL)
	{
		printf("malloc soap error \n");

		return -1;
	}

	//设置命名空间,就是xml文件的头
	soap_set_namespaces(soap, namespaces);

	//超出5s没数据就推出,超时时间
	soap->recv_timeout = 5;

	//将header设置为soap消息,头属性,暂且认为是soap和header绑定
	soap_default_SOAP_ENV__Header(soap, &header);
	header.wsa__MessageID = uuid_string;

	header.wsa__To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery";
	header.wsa__Action = "http://schemas.xmllocal_soap.org/ws/2005/04/discovery/Probe";
	//设置soap头消息的ID
	soap->header = &header;

	/* 设置所需寻找设备的类型和范围,二者至少设置一个
		否则可能收到非ONVIF设备,出现异常
	 */

	//设置soap消息的请求服务属性
	soap_default_wsdd__ScopesType(soap, &sScope);
	sScope.__item = "onvif://www.onvif.org";
	soap_default_wsdd__ProbeType(soap, &req);
	req.Scopes = &sScope;

	/* 设置所需设备的类型,ns1为命名空间前缀,在wsdd.nsmap 文件中 
	   {"tdn","http://www.onvif.org/ver10/network/wsdl"}的tdn,如果不是tdn,而是其它,

       例如ns1这里也要随之改为ns1 
	*/
	req.Types = "ns1:NetworkVideoTransmitter";

	//调用gSoap接口 向 239.255.255.250:3702 发送udp消息
	result = soap_send___wsdd__Probe(soap,  "soap.udp://239.255.255.250:3702/", NULL, &req);

	if(result == -1)
	{
		printf("soap error: %d, %s, %s \n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
		result = soap->error;
	}
	else
	{
		do{
			printf("%s: %d, begin receive probematch... \n", __FUNCTION__, __LINE__);
			printf("count = %d \n", count);

			//接收 ProbeMatches,成功返回0,错误返回-1
			result = soap_recv___wsdd__ProbeMatches(soap, &resp);
			printf(" --soap_recv___wsdd__ProbeMatches() result=%d \n",result);
			if(result == -1)
			{
				printf("Find %d devices!\n", count);
				break;
			}
			else
			{
				//读取服务器回应的Probematch消息
				printf("soap_recv___wsdd__Probe: __sizeProbeMatch = %d \n", resp.wsdd__ProbeMatches->__sizeProbeMatch);
				printf("Target EP Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);
				printf("Target Type : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Types);
				printf("Target Service Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);
				printf("Target Metadata Version: %d \n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);
				printf("Target Scope Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item);	
				count++;
			}	
		}while(1);
	}
	//清除soap
	// clean up and remove deserialized data
	soap_end(soap);
	//detach and free runtime context
	soap_free(soap);

	return result;
}

5.3 makefile

#我的工程目录中的头文件
INCLUED = -I ./
CC = gcc -g -DWITH_NONAMESPACES
SERVER_OBJS = soapC.o stdsoap2.o  soapClient.o soapServer.o  deviceserver.o  wsddapi.o  wsaapi.o
CLIENT_OBJS = soapC.o stdsoap2.o soapClient.o deviceprobe.o

all: server client
server:$(SERVER_OBJS)
	$(CC) $(INCLUED) -o server $(SERVER_OBJS)
client:$(CLIENT_OBJS)
	$(CC) $(INCLUED) -o client $(CLIENT_OBJS)
	
clean:
	rm -f server client *.o

接下来对着他们之间的通信数据包来理解上面的字段信息

//client
<?xml version="1.0" encoding="utf-8"?>

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" 
					xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" 
					xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
					xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
					xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" 
					xmlns:wsdd="http://schemas.xmlsoap.org/ws/2005/04/discovery" 
					xmlns:tdn="http://www.onvif.org/ver10/network/wsdl">
  <SOAP-ENV:Header>
  <!-标签>
    <wsa:MessageID>464A4854-4656-5242-4530-110000000000</wsa:MessageID>
    <!-接收者地址>
	<wsa:To SOAP-ENV:mustUnderstand="true">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>
    <!-要求接收者行为>
	<wsa:Action SOAP-ENV:mustUnderstand="true">http://schemas.xmllocal_soap.org/ws/2005/04/discovery/Probe</wsa:Action>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <wsdd:Probe>
      <wsdd:Types>ns1:NetworkVideoTransmitter</wsdd:Types>
      <wsdd:Scopes>onvif://www.onvif.org</wsdd:Scopes>
    </wsdd:Probe>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>



//server
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" 
					xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" 
					xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
					xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
					xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" 
					xmlns:wsdd="http://schemas.xmlsoap.org/ws/2005/04/discovery" 
					xmlns:tdn="http://www.onvif.org/ver10/network/wsdl">
  <SOAP-ENV:Header>
    <wsa:MessageID>uuid:052d5632-3448-26bb-4d2e-08f32b881be6</wsa:MessageID>
    <wsa:To SOAP-ENV:mustUnderstand="true">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>
    <wsa:Action SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>
    <wsdd:AppSequence InstanceId="0" MessageNumber="10"/>
  </SOAP-ENV:Header>
  
  <SOAP-ENV:Body>
    <wsdd:ProbeMatches>
      <wsdd:ProbeMatch>
        <wsa:EndpointReference>
          <wsa:Address>urn:uuid:464A4854-4656-5242-4530-313035394100</wsa:Address>
        </wsa:EndpointReference>
        <wsdd:Types>ns1:NetworkVideoTransmitter</wsdd:Types>
        <wsdd:Scopes>onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/type/audio_encoder onvif://www.onvif.org/hardware/IPC-model onvif://www.onvif.org/name/IPC-model</wsdd:Scopes>
        <wsdd:XAddrs>http://10.0.0.47/onvif/device_service</wsdd:XAddrs>
        <wsdd:MetadataVersion>10</wsdd:MetadataVersion>
      </wsdd:ProbeMatch>
    </wsdd:ProbeMatches>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
//

结尾

我在csdn上和码云上都有上传,有积分的就在csdn上下载,没积分的就在码云上下载
工程连接
github地址

码云地址
最后需要查看的资料
[ws-addressing](https://www.w3.org/Submission/ws-addressing/)
WS-Discovery:我会放到工程目录中

https://www.onvif.org/profiles/specifications/

https://www.onvif.org/onvif/ver20/util/operationIndex.html
参考
https://blog.csdn.net/u012084827/article/month/2013/09

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值