最近自己在学习onvif摄像头相关的协议,然后就开始学习了下webservice服务,然后整理下自己对它的理解,以及参考网上的一些教程,实现了webservice服务。
WebService介绍
首先我们来谈一下为什么需要学习webService这样的一个技术吧…
问题一
如果我们的网站需要提供一个天气预报这样一个需求的话,那我们该怎么做?????
天气预报这么一个功能并不是简单的JS组件就能够实现的,它的数据是依赖数据库分析出来的,甚至需要卫星探测…我们个人建站是不可能搞这么一个数据库的吧。
那么既然我们自己干不了,我们可以去找别人吗???我们从搜索引擎搜索,可以发现很多提供天气预报的网站,但是它返回的是一个网页,而我们仅仅需要的是对应的数据!
我们可能就在想,我们能不能仅仅只要它返回的数据,而并不是经过加工处理后返回的网页呢??
于是乎,webService就诞生了,webservice就是一个部署在Web服务器上的,它向外界暴露出一个能够通过Web进行调用的API。也就是说:当我们想要获取天气预报的信息,我们可以调用别人写好的service服务,我们调用就能够得到结果了!
问题二
可是我们写网站主流的就有好几个平台:Java、.net、PHP等等,那么部署在Web服务器上的服务器也就是webserice怎么能够就让我们不同的平台都能够调用呢??
我们知道java、.net这样的平台他们语言的基本数据类型、复杂数据类型就可能不一样,那么怎么能够实现调用的呢???
来引用一段话
大家在写应用程序查询数据库时,并没有考虑过为什么可以将查询结果返回给上层的应用程序,甚至认为,这就是数据库应该做的,其实不然,这是数据库通过TCP/IP协议与另一个应用程序进行交流的结果,而上层是什么样的应用程序,是用什么语言,数据库本身并不知道,它只知道接收到了一份协议,这就是SQL92查询标准协议。
无论是Java、.net、PHP等等的平台,只要是网页开发都是可以通过http协议来进行通信的,并且返回的数据要是通用的话,那么我们早就学过这样的一种技术【XML】
所以webservice实际上就是http+XML
对webservice的理解
WebService,顾名思义就是基于Web的服务。它使用Web(HTTP)方式,接收和响应外部系统的某种请求。从而实现远程调用.
我们可以调用互联网上查询天气信息Web服务,然后将它嵌入到我们的程序(C/S或B/S程序)当中来,当用户从我们的网点看到天气信息时,他会认为我们为他提供了很多的信息服务,但其实我们什么也没有做,只是简单调用了一下服务器上的一段代码而已。
学习WebService可以将你的服务(一段代码)发布到互联网上让别人去调用,也可以调用别人机器上发布的WebService,就像使用自己的代码一样.。
webService相关术语
- 名词1:XML. Extensible Markup Language -扩展性标记语言
- XML,用于传输格式化的数据,是Web服务的基础。
- namespace-命名空间。
- xmlns=“http://itcast.cn” 使用默认命名空间。
- xmlns:itcast=“http://itcast.cn”使用指定名称的命名空间。
- 名词2:WSDL – WebService Description Language – Web服务描述语言。
- 通过XML形式说明服务在什么地方-地址。
- 通过XML形式说明服务提供什么样的方法 – 如何调用。
- 名词3:SOAP-Simple Object Access Protocol(简单对象访问协议)
- SOAP作为一个基于XML语言的协议用于有网上传输数据。
- SOAP = 在HTTP的基础上+XML数据。
- SOAP是基于HTTP的。
- SOAP的组成如下:
- Envelope – 必须的部分。以XML的根元素出现。
- Headers – 可选的。
- Body – 必须的。在body部分,包含要执行的服务器的方法。和发送到服务器的数据。
对于c++来说我们发布webservice服务,也是有库支撑的,那就是gsoap,我用的gsoap的版本是
GSoap是一个开源的工具,功能非常强大,各位可以到网络上搜索学习看看这个不错的工具,gSOAP编译工具提供了一个SOAP/XML关于C/C++语言的实现,从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多。绝大多数的C++web服务工具包提供一组API函数类库来处理特定的SOAP数据结构,这样就使得用户必须改变程序结构来适应相关的类库。这里我主要通过C/S模式调用GSOAP来实现服务端和客户端。首先我的目标是提供一组简单数学的接口:加减乘除四个接口。
在使用GSOAP提供几个接口之前,首先带大家熟悉一下Windows下GSOAP里面携带的两个工具:wsdl2h.exe以及soapcpp2.exe,两工具可以在下载的gsoap2.8.29(下载可以到开源社区去下载:http://sourceforge.net/projects/gsoap2)的gsoap/bin/win32目录找到,当然这是gsoap在windows提供的两个可直接的工具,下面我们先来认识一下这两个简单易用的工具:
wsdl2h工具: 正如其名wsdl到h,也就是webservice description language(.wsdl文件)转换成.h的头文件工具,换句话说就是用来转为.h的工具,这里我们不介绍wsdl文件如何编写,也不介绍wsdl转h功能,因为我们自己已经写好的.h文件,所以用不到,如果想了解,请到onvif官网下载相关wsdl然后用这个工具试试!具体功能如下:
(1)、它仅仅负责生成wsdl中描述的相应的头文件,这些头文件不能直接使用,需要经过soapcpp2.exe转换为gsoap生成的接口后才能使用
(2)、输入条件为一个或多个wsdl或xsd文件或者相应的URL路径,如果输入wsdl或xsd则输出为第一个名称的.h文件,如果为URL则默认为标准输出
携带参数:
-s: don't generate STL code 也就是不使用标准模板库,如果不使用-s参数那么默认使用STL库,也就是必须将以来的文件./gsoap/stdsoap2.h和stdsoap2.cpp拷贝至应用的程序当中。
-c: generate C source code c意思是生成c文件接口,默认是c++头文件
-o:output to file 指定输出文件名称,可以重命名生成的文件名称
-t:use type map file instead of the default file typemap.dat 使用类型定义文件,可以在typemap.dat(自带的)中修改生成选项,生成符合要求的头文件,如编码
当然,该用具还有其他n个参数使用,此处仅仅介绍几个常用的,其他控制参数自己去了解学习。
soapcpp2.exe:该工具功能是将我们提供或有wsdl2h生成的.h的头文件(接口)转换为gsoap提供的形式的接口和rpc代理相关的代码和框架(当然我没有具体去了解,vs中你可以搜索到携带相应接口名的其他代理方法并且有相应的实现)
相关参数如下:
(1)、-1生成SOAP1.1协议的代码
(2)、-2生成SOAP1.2协议的代码
(3)、-C只是生成客户端相关的代码
(4)、-S只是生成服务端相关的代码
(5)、-i generate C++ service proxies and objects inherited from soap struct 生成对象为soap的子类,会影响生成文件的使用方法。如 class SOAP_CMAC BasicCMIRPService : public soap
其他参数此处不再介绍,如需要更详细解释,请到官网查询相应工具使用介绍。
好,学习两个工具之后,我们就可以将以上工具派上用场了,当然我们这里不需要wsdl2h工具,因为我们是C++程序员,头文件我们自己编写提供即可,不需要编写wsdl在转为.h文件,省去了不少麻烦,我们直接使用soapcpp2工具将头文件转对应的头即可,首先服务端提供加减乘除4个接口,客户端调用加减乘除4个接口,约定接口如下:
int Add(int nNum1, int nNum2, int* pResult);
int Sub(int nNum1, int nNum2, int* pResult);
int Mul(int nNum1, int nNum2, int* pResult);
int Div(int nNum1, int nNum2, int* pResult);
返回值我们以参数地址形式提供出去而不是以函数返回值形式提供跟gsoap生成接口有关,它生成接口调用的时候返回值表示传入参数是否正确或调用成功用到了返回值,所以我们自己的接口为了不覆盖它的使用得这么写,当然,形式变化而已,无所谓!既然客户端和服务器都以改4接口为通信接口,那么这4个接口就作为我们要转换为gsoap代理或框架时soapcpp2用到的头文件的声明,新建服务端程序,新建win32工程项目:GSoapServer空项目,添加类GSoapServer,然后删除该类所有声明和实现(目的就是用GSaopServer.h和GSaopServer.cpp两个配对文件,不需要相应的类),在GSoapServer.h中声明以上几个函数:
#pragma once
int Add(int nNum1, int nNum2, int* pResult);
int Sub(int nNum1, int nNum2, int* pResult);
int Mul(int nNum1, int nNum2, int* pResult);
int Div(int nNum1, int nNum2, int* pResult);
在GSoapServer.cpp中添加main函数
#include "GSoapServer.h"
int main(int argc, char** argv)
{
return 0;
}
编译该项目,保证编译通过无错误,接下来见gsoap2.8工具目录下的./gsoap/bin/win32/gsoapcpp2.exe 拷贝至与GSoapServer.h相同目录下,使用命令行cmd,进入该目录
如:
键入:cd E:/project/C++/test/GSoapServer/GSoapServer
键入:E:
使用soapcpp2.exe: soapcpp2.exe GSoapServer.h
此时,由于我们没有指定-C或-S,它会为我们生成客户端和服务端的所有代码,如果只是生成客户端代码添加-C参数,此时会生成soapH.c、soapC.cpp、soapStub.h、soapClient.cpp soapClientLib.cpp代码(加上我们以来的stl库stdsoap2.h和stdsoap2.cpp供7个文件,当然包含被转的GSoapServer.h文件就是8个,此处生成后要不要GSaopServer.h已经无所谓了);如果只想生成服务端的代码,那么添加-S参数,此时会生成soapH.c、soapC.cpp、soapStub.h、soapServer.cpp soapServerLib.cpp以及soap.nsmap文件,也就是8个文件(不计算GsopServer.h被转换的头文件)
其中soap.h和soapC.cpp是具体的实现,soapStub.h为代理的声明(服务端我们需要实现声明的4个接口-该四个接口和我们约定的不太一样,第一个参数多了
soap*对象指针)
对于服务端,我们将soap.h soapC.cpp soapStub.h soapServer.cpp以及依赖的stl库stdsoap2.h和stdsoap2.cpp 五个文件添加的项目中(soapServerLib.cpp不需要,否则会出错),添加后所有的cpp属性设置预编译头为“不使用预编译头”,最后找到soapStub.h文件最后几行会发现我们约定的即可接口声明(接口稍微有变化,多了soap*参数),拷贝到GSoapServer.cpp工程目录的main函数之前,并且实现之:
/** Web service operation 'Add' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Add(struct soap*, int nNum1, int nNum2, int *pResult)
{
*pResult = nNum1 + nNum2;
return SOAP_OK;
}
/** Web service operation 'Sub' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Sub(struct soap*, int nNum1, int nNum2, int *pResult)
{
*pResult = nNum1 - nNum2;
return SOAP_OK;
}
/** Web service operation 'Mul' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Mul(struct soap*, int nNum1, int nNum2, int *pResult)
{
*pResult = nNum1 * nNum2;
return SOAP_OK;
}
/** Web service operation 'Div' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Div(struct soap*, int nNum1, int nNum2, int *pResult)
{
if(0 == nNum2)
{
*pResult = 0;
return -1;
}
*pResult = nNum1 / nNum2;
return SOAP_OK;
}
编译,发现没有错误,如果有预编译错误,设置cpp属性去掉预编译头即可,切记不需要soapServerLib.cpp生成的文件,soapServer.cpp是必要的,它实现了soap_serve服务,也就是我们服务器提供的服务!
具体的服务器main函数实现如下:
#include "GSoapServer.h"
#include “soap.nsmap” // 服务端与客户端必须包含的文件
/** Web service operation 'Add' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Add(struct soap*, int nNum1, int nNum2, int *pResult)
{
*pResult = nNum1 + nNum2;
return SOAP_OK;
}
/** Web service operation 'Sub' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Sub(struct soap*, int nNum1, int nNum2, int *pResult)
{
*pResult = nNum1 - nNum2;
return SOAP_OK;
}
/** Web service operation 'Mul' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Mul(struct soap*, int nNum1, int nNum2, int *pResult)
{
*pResult = nNum1 * nNum2;
return SOAP_OK;
}
/** Web service operation 'Div' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Div(struct soap*, int nNum1, int nNum2, int *pResult)
{
if(0 == nNum2)
{
*pResult = 0;
return -1;
}
*pResult = nNum1 / nNum2;
return SOAP_OK;
}
int main(int argc, char**argv)
{
struct soap math_service;
soap_init(&math_service);
// bind端口返回SOCKET套接字-雷同socket套接口函数服务器监听过程
if(soap_bind(&math_service, NULL, 8686, 100) <0)
{
soap_print_fault(&math_service, stderr);
return -1;
}
fprintf(stderr, "start math webservice ...\n");
// 当然这里可以用多个soap在多个现成中使用soap_new创建新的soap,每个线程一个
while(true)
{
int nClient = (int)soap_accept(&math_service);
if(nClient < 0)
{
soap_print_fault(&math_service, stderr);
return -1;
}
fprintf(stderr, "client[%d] connect success. ..\n, nClient);
soap_serve(&math_service);// provide service
soap_end(&math_service);// end service
}
return 0;
}
启动服务端程序,打开IE输入:http://localhost:8686/
会发现IE返回:
<SOAP-ENV:Fault xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<faultcode>at source</faultcode>
<faultstring>HTTP GET method not implemented</faultstring>
</SOAP-ENV:Fault>
说明,服务端启动成功!
接下来写客户端程序:
客户端很简单,因为所有的文件我们已经用soapcpp2.exe工具生成了,新建GSoapClient工程,空工程或默认的win32控制台都可以,将服务端生成的文件soapH.h soapStub.h stdsoap2.h以及对应的源文件soapC.cpp soapClient.cpp stdsoap2.cpp 拷贝至客户端工程目录下并添加文件至项目中,然后设置cpp对应的属性,去掉预编译头文件,包含soap.nsmap文件,实现并调用webservice接口如下:
// GSoapClient.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#include "soap.nsmap"
int main(int argc, char** argv)
{
struct soap math_soap;
soap_init(&math_soap);
int nResult = 0;
const char* pWebService = "http://127.0.0.1:8686";
soap_call_Add(&math_soap, pWebService, "", 60, 30, &nResult);
if (math_soap.error)
{
soap_print_fault(&math_soap, stderr);
}
printf("webservice:60+30=%d\n", nResult);
soap_call_Sub(&math_soap, pWebService, "", 60, 30, &nResult);
if (math_soap.error)
{
soap_print_fault(&math_soap, stderr);
}
printf("webservice:60-30=%d\n", nResult);
soap_call_Mul(&math_soap, pWebService, "", 60, 30, &nResult);
if (math_soap.error)
{
soap_print_fault(&math_soap, stderr);
}
printf("webservice:60*30=%d\n", nResult);
soap_call_Div(&math_soap, pWebService, "", 60, 30, &nResult);
if (math_soap.error)
{
soap_print_fault(&math_soap, stderr);
}
printf("webservice:60/30=%d\n", nResult);
soap_end(&math_soap);
soap_done(&math_soap);
system("pause");
return 0;
}
服务器启动后,启动客户端,客户端可以进行显示相关的输出结果,但是最后还是有点问题没有弄清楚,就是客户端可以调用我的服务,但是需要我给对方一个.h文件,然后对方通过gsoap来生成客户端程序,然后通过代理进行调用,现在还不能通过工具生成对应的wsdl文件,让对方直接访问我的网址就得到wsdl,然后生成.h,再进行上面的操作,希望有能够实现的下面留言回答下,
大家共同学习,本文主要参考两篇文章得到
https://blog.csdn.net/sunxiaopengsun/article/details/77008132