Onvif协议:使用gSOAP创建SOAP调用实例
预备知识
ONVIF规范中设备管理和控制部分所定义的接口均以Web Services的形式提供。ONVIF规范涵盖了完全的XML及WSDL的定义。每一个支持ONVIF规范的终端设备均须提供与功能相应的Web Service。服务端与客户端的数据交互采用SOAP协议。
ONVIF中的其他部分比如音视频流则通过RTP/RTSP进行 。
那么WebServices、SOAP、WSDL、gSOAP又都是什么?
什么是Onvif
假如我们需要开发一个linux上的app,这个app需要与远端的Web服务有一个交互,比如获取一个运算结果、或者是天气等,那么我们就需要使用WebServices。
Web Services可以概述为:
- Web Services 可以将应用程序转换为网络应用程序:通过使用 Web Services,应用程序可以向全世界发布信息,或提供某项功能。
- Web Services 可以被其他应用程序使用: 通过 Web Services,会计部门的 Win 服务器可以与 IT 供应商的 UNIX 服务器相连接。
- 基本的 Web Services 平台是 XML+HTTP:Web services 使用 XML 来编解码数据,并使用 SOAP 来传输数据。
SOAP又是什么?
SOAP 是基于 XML 的简易协议,可使应用程序在 HTTP 之上进行信息交换。或者更简单地说:SOAP 是用于访问网络服务的协议。
对于应用程序开发来说,使程序之间进行因特网通信是很重要的。目前的应用程序通过使用远程过程调用(RPC)在诸如 DCOM 与 CORBA 等对象之间进行通信,但是 HTTP 不是为此设计的。RPC 会产生兼容性以及安全问题;防火墙和代理服务器通常会阻止此类流量。通过 HTTP 在应用程序间通信是更好的方法,因为 HTTP 得到了所有的因特网浏览器及服务器的支持。SOAP 就是被创造出来完成这个任务的。
SOAP 提供了一种标准的方法,使得运行在不同的操作系统并使用不同的技术和编程语言的应用程序可以互相进行通信。
如何实现SOAP?
ONVIF标准是使用SOAP方式实现的Web Services,包括很多概念,比如SOAP、HTTP、XML,RPC等等。辣么多东东,全部要自己码代码实现吗?当然不用,我们不必自己造轮子,有现成的工具会帮我们自动生产大部分的代码框架。
这样的工具有很多,比如:
- Apache CXF工具,适用于JAVA语言开发者。
- gSOAP工具,适用于C/C++语言开发。
我这里使用的是c/c++,因此gSOAP。
gSOAP
gSOAP官方网址:http://www.cs.fsu.edu/~engelen/soap.html
gSOAP开源版下载网址(最新版本):http://sourceforge.net/projects/gsoap2
gSOAP开源版下载网址(历史版本):https://sourceforge.net/projects/gsoap2/files/gSOAP/
gSOAP 编译工具提供了一个SOAP关于C/C++ 语言的实现,从而让C/C++语言开发Web Services服务端或客户端程序的工作变得轻松了很多。甚至,即使你对Web Services不甚了解都没有关系,有了gSOAP这样的工具,你也能开发基于SOAP方式实现的Web Services客户端。
gSOAP到底会自动生成哪些框架代码,下图中浅绿色框中的部分就是自动生成的代码。
gSOAP工具转换原理
gSOAP工具根据WSDL文档,自动生成C/C++语言的客户端/服务端框架代码。这其中有两个工具很重要,wsdl2h和soapcpp2。wsdl2h工具根据WSDL文成C/C++头文件,而soapcpp2工具则是根据该头文件生成C/C++的框架源码。
gSOAP工具可以在Windows、Linux和Macosx操作系统下运行,gSOAP工具包中自带有Windows和Macosx操作系统的wsdl2h和soapcpp2可执行文件,而Linux操作系统的,得自己编译。
通过实验证实,用Windows和Linux工具生成的框架代码,是一样样的,没有区别。
使用方式
开发Web服务程序,需使用gSOAP生成服务器端和客户端代码框架(通常情况下之需要实现server端或者实现client,因为另一端通常是别人做好的,比如ipnc中的onvif,实现的server端)。我们有两种做法:
- 编写WSDL,使用wsdl2h生成头文件,再soapcpp2生成框架代码;
- 编写头文件,使用soapcpp2生成框架代码;
这两种方式,结果是一样的,最终都有产生头文件,并生成代码。不同在于,在项目的开发中需要维护的文件不同,前者是需要维护WSDL文件,后者维护头文件。
SOAP调用示例
1、搭建gSoap环境
下载gSoap包
地址:https://sourceforge.net/projects/gsoap2/
编译gSoap包
$ unzip gsoap_2.8.108.zip
$ cd gsoap-2.8
配置
$ ./configure --prefix /home/oceanstar/workspace/cpp/Onvif/gsoap # 可以指定安装目录
# ./configure --prefix /home/oceanstar/workspace/cpp/Onvif/gsoap2-8-105 --disable-ssl --enable-gnutls
# 禁止ssl ,onvif鉴权需要用到鉴权,如果禁止,请用其他代替
# 千万不要禁止ssl,也不能用gnutls代替,onvif的鉴权引用中使用的就是openssl
编译
$ make
make错误:
../../ylwrap:行176: yacc: 未找到命令
解决: 安装bison。(yacc是一个生成语法分析器的工具,CentOS下是用flex和bison来分别代替lex和yacc的,安装bison:sudo yum install bison)
$ yum install bison
重新make
$ ./configure --prefix /home/oceanstar/workspace/cpp/Onvif/gsoap
$ make
make错误:
missing:行81: flex: 未找到命令
解决:
$ yum install flex
重新make
$ ./configure --prefix /home/oceanstar/workspace/cpp/Onvif/gsoap
$ make
最后:
$ make install
结果:
我们可以在/home/oceanstar/workspace/cpp/Onvif/gsoap 目录下发现生成了如下目录:
进入bin目录下,里面就是生成的工具了
wsdl2h的作用是根据WSDL文件生成一个gsoap用到的头文件,wsdl2h 常用指令:
- -o filename.h 将wsdl转化为filename.h头文件。
- -s 不生成STL代码
- -c 生成纯C风格的头文件,这将去除C++的一些特性
- -n name 使用name代替默认前缀ns
- -t filename.dat 使用filename.dat代替默认的typemap.dat文件
- -zX 兼容之前的X版本
soapcpp2的作用是根据头文件自动生成调用远程 SOAP服务的客户端代码(称为存根:Stub)和提供SOAP服务的框架代码(称为框架:Skeleton),另外它也能从头文件生成WSDL文件。其中,soapH.h and soapC.cpp包含了数据类型的描述,soapClient.cpp给客户端使用,soapServer.cpp给服务端使用。
- -i 生成server的proxy和object,这种object继承于soap struct。
- -j 和-i类似,区别在于生成的代理类不继承于soap struct,而是包含了包含了一个soap结构的指针。此种方式生存的代理类便于互相通信
- -C 仅生成客户端client代码
- -S 仅生成服务端server代码
- -x 不生成xml文件。不用此项的话,将对头文件中定义的每个operation生成一个描述性的xml文件
- -L 不生成soapClientLib文件和soapServerLib文件
- -p name 修改文件名前缀,代替soap
- -q name 指定代理类和对象使用的名空间name,包含文件名前缀
命令行模式下敲入命令:./wsdl2h -V
查看gSOAP软件版本,有版本出现则安装成功。
官方安装方法
2、编写头文件:add.h
在这里我们不需要wsdl的文件,可以直接从.h文件来生成代码。我们定义一个函数声明文件,用来定义接口函数,名称为add.h
//gsoapopt cw
//gsoap ns2 schema namespace: urn:add
//gsoap ns2 schema form: unqualified
//gsoap ns2 service name: add
//gsoap ns2 service type: addPortType
//gsoap ns2 service port:http://websrv.cs.fsu.edu/~engelen/addserver.cgi
//gsoap ns2 service namespace: urn:add
//gsoap ns2 service transport: http://schemas.xmlsoap.org/soap/http
//gsoap ns2 service method-style: add rpc
//gsoap ns2 service method-encoding: add http://schemas.xmlsoap.org/soap/encoding/
//gsoap ns2 service method-action: add ""
int ns2__add( int num1, int num2, int* sum );
3、产生代码框架
我们执行一下命令,自动生成一些远程调用需要的文件。(先将他们加如到系统环境变量中)
./soapcpp2 -c -x add.h
- -c是产生纯C代码,如果提示找不到typemap.dat,将gsoap-2.8\gsoap下的typemap.dat复制到当前目录就可以了。
通过上列命令我们会得到如下文件:
先大概记住他们的名字,将来会提到他们。
4、添加服务端代码,创建文件:addserver.c
#include "soapH.h"
#include "add.nsmap"
int main(int argc, char **argv)
{
int m, s;
struct soap add_soap;
soap_init(&add_soap);
soap_set_namespaces(&add_soap, namespaces);
if (argc < 2) {
printf("usage: %s <server_port> \n", argv[0]);
exit(1);
} else {
m = soap_bind(&add_soap, NULL, atoi(argv[1]), 100);
if (m < 0) {
soap_print_fault(&add_soap, stderr);
exit(-1);
}
fprintf(stderr, "Socket connection successful: master socket = %d\n", m);
for (;;) {
s = soap_accept(&add_soap);
if (s < 0) {
soap_print_fault(&add_soap, stderr);
exit(-1);
}
fprintf(stderr, "Socket connection successful: slave socket = %d\n", s);
soap_serve(&add_soap);
soap_end(&add_soap);
}
}
return 0;
}
#if 1
int ns2__add(struct soap *add_soap, int num1, int num2, int *sum)
{
*sum = num1 + num2;
return 0;
}
#endif
5、添加客户端代码,创建文件:addclient.c
#include "soapStub.h"
#include "add.nsmap"
int add(const char *server, int num1, int num2, int *sum)
{
struct soap add_soap;
int result = 0;
soap_init(&add_soap);
soap_set_namespaces(&add_soap, namespaces);
soap_call_ns2__add(&add_soap, server, NULL, num1, num2, sum);
printf("server is %s, num1 is %d, num2 is %d/n", server, num1, num2);
if (add_soap.error) {
printf("soap error: %d, %s, %s\n", add_soap.error, *soap_faultcode(&add_soap), *soap_faultstring(&add_soap));
result = add_soap.error;
}
soap_end(&add_soap);
soap_done(&add_soap);
return result;
}
6、写客户端测试代码,创建文件:addtest.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int add(const char *server, int num1, int num2, int *sum);
int main(int argc, char **argv)
{
int result = -1;
char server[128] = {
0};
int num1;
int num2;
int sum;
if (argc < 4) {
printf("usage: %s <ip:port> num1 num2 \n", argv[0]);
exit(1);
}
strcpy(server,argv[1]);
num1 = atoi(argv[2]);
num2 = atoi(argv[3]);
result = add(server, num1, num2,&sum);
if (result != 0) {
printf("soap error, errcode=%d\n", result);
} else {
printf("%d + %d = %d\n", num1, num2, sum);
}
return 0;
}
7、编写Makefile
编译前,先将gsoap-2.8\gsoap【下载的gsoap源码解压之后的目录下】目录下的stdsoap2.c和stdsoap2.h复制到当前目录下,它提供了对SOAP协议的简单调用。
编写Makefile
GSOAP_ROOT = /home/oceanstar/workspace/cpp/gsoap-2.8/gsoap
CC = gcc -g -DWITH_NONAMESPACES
INCLUDE = -I$(GSOAP_ROOT)
SERVER_OBJS = soapC.o stdsoap2.o soapServer.o addserver.o
CLIENT_OBJS = soapC.o stdsoap2.o soapClient.o addclient.o addtest.o
all: server
server: $(SERVER_OBJS)
$(CC) $(INCLUDE) -o addserver $(SERVER_OBJS)
client: $(CLIENT_OBJS)
$(CC) $(INCLUDE) -o addtest $(CLIENT_OBJS)
clean:
rm -f *.o addtest
8、编译:
$ make server # 编译服务端 得到addserver
$ make client # 编译服务端 得到addtest
9、测试
一个最简单的soap调用的例子完成了。
10、实例分析
服务端代码
下面我们来分析上面的例子,刚才我们只是创建一个add.h头文件,在add.h头文件中声明了一个函数:
int ns2__add( int num1, int num2, int* sum );
其他所有的的代码都是一句他来生成的。那么这个的实体在哪?对,就是在需要我们自己添加的addserver.c中:
但是它好像多了一个struct soap类型的参数,这是soap全局运行环境,所有的函数都第一个包含这个参数。注意上面的Makefile,不管是编译server还是编译client都是没有用到刚才的add.h文件的。ns2__add真正的声明在自动产生的soapStub.h中
然后在自动产生的soapServer.c中被soap_serve_ns2__add()函数调用。这样,就将真正的加法运算的ns2__add函数和soap代码框架联系了起来。那么,在客户端的代码中又是怎样来调用这个远程函数的呢?
客户端代码
在刚才添加的addtest.c中main函数中调用一个简单的add函数
这个函数的实现也是我们自己添加的,在addclient.c中:
这个函数有些复杂,因为它把客户端的调用和soap联系了起来,还记得吗,我们编译server和client的时候复制了两个文件stdsoap2.h和stdsoap2.c,这里面的soap_init() soap_end()等函数来自他们。stdsoap2提供了soap协议的简单操作,之需要简单的函数调用就能完成远程的函数调用。注意soap_call_ns2__add(),它同样在soapStub.h中声明,只不过是Client-Side Call Stubs,不明白stub意思的可以搜索rpc
这个函数的实现在自动产生的soapClient.c源文件中。同样不需要我们实现。
这样就可以通过调用gSOAP提供的stdsoap2的soap_init和自动产生的soap_call_ns2__add就实现了远程主机上的ns2__add函数的调用
示例二
https://blog.csdn.net/benkaoya/article/details/72452968
总结
开发基于SOAP方式的Web Services,不需要自己实现代码框架,有诸如gSOAP、Apache CXF这样的工具会帮我们实现。
还没有评论,来说两句吧...