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