一、相关知识简介
关于soap:
1、SOAP 是基于 XML 的简易协议,可使应用程序在 HTTP 之上进行信息交换。或者更简单地说:SOAP 是用于访问网络服务的协议。
2、SOAP 提供了一种标准的方法,使得运行在不同的操作系统并使用不同的技术和编程语言的应用程序可以互相进行通信。
3、关于Soap,它是一种简单对象访问协议,是一种轻量的、简单的、基于 XML 的协议,它被设计成在 WEB 上交换结构化的和固化的信息。它是一种通信协议,用于Web应用程序之间的因特网通信,它提供一组不受平台和语言限制的方法使应用程序得以相互之间进行Intertnet通信。而gSoap可以看做是对soap的一个实现封装,它隐藏了在调用WebService中的大量实现细节,使得C++调用WebService变得较为简单。
关于WSDL:
1、WSDL,最初弄的WSDL的时候被这个WSDL弄的焦头烂额的。这个文件实际上就是WebService的对外接口文件,它是一个XML文件,描述了WebService接口的外部方法和抽象方法,以及各协议下的相关调用,是一个用来描述Web服务和说明如何与Web服务通信的XML语言,为用户提供详细的接口说明书。对于WSDL文件,只需要在浏览器中打开,即可查看到WebService接口的外部供调用的方法。
关于gsoap:
1、gSOAP是一个开发SOAP和XML应用(它们组成了webservice)的工具,在英文中叫toolkit。它是跨平台的,webservice的客户端和服务器端,都可以用它来辅助开发。
2、开发Web服务程序,需使用gSOAP生成服务器端和客户端代码框架(通常情况下之需要实现server端或者实现client,因为另一端通常是别人做好的,比如ipnc中的onvif,实现的server端)
3、在编写客户端之前我们必须提供webservice的服务器端。
相关链接:
SOAP扫盲: http://www.runoob.com/soap/soap-tutorial.html
gSOAP 2.8.34 User Guide:http://www.cs.fsu.edu/~engelen/soapdoc2.html
gsoap主页:http://www.cs.fsu.edu/~engelen/soap.html
gsoap官网: http://gsoap2.sourceforge.net/ 遇到问题时,官网往往是最能提供帮助的地方。
下载地址:http://sourceforge.net/projects/gsoap2
示例: cal.h文件
//gsoap ns service name: itoa
//gsoap ns service protocol: SOAP
//gsoap ns service style: rpc
//gsoap ns service namespace: http://localhost:8087/itoa?wsdl
//gsoap ns service location: http://localhost:8087
//gsoap ns service encoding: encoded
//gsoap ns schema namespace: urn:itoa
int ns__itoa(int i, char **a);
int ns__add(double a, double b, double& result);
1、service location 如果在客户端调用接口方法时,没有输入服务端地址,方法执行时将自动调用此处的地址做为服务器地址。
2、 gSoap对“_”和“__”(下划线、双下划线)有特殊用法,接口定义时函数名前要加上命名空间名和双下划线
举例:加法运算 int add(int num1, int num2, int &num3);
在接口头文件定义时要写成如下格式 int ns__add(int num1, int nmu2, int &num3); 中
注意事项:
1>"ns"是命名空间名称,此名称可以自定义成其它名字 但其后"urn:itoa"不可随意更改,这个是要与服务端统一的
2>"__"是编译时识别符号,如果没有的话xml文件是编译不出来的。
3> "_"(单下划线)定义名称中要慎用,自己定义的变量或函数名称中使用下划线的地方要在下划线后加上"USCORE";
例 定义 ns__add_sum(int num_1, int num2, int &num_3);
要写成如下形式:ns__add_USCOREsum(int num_USCORE1, int num2, int &num_USCORE3);
4>"add"是接口函数名 这个是客户端与服务器统一的接口名字。
5> 函数内参数变量类型和变量名 为客户端与服务器统一的接口。
6> 函数中只有最后一个参数是输出参数,前面的都是输入参数。
7> 多个参数传出(传回),在接口中必须使用结构体
typedef char * xsd__string;
typedef long xsd__int;
struct ns__personResponse
{
xsd__int age;
xsd__string name;
xsd__string address;
};
int ns__person( xsd__string buf_in, struct ns__personResponse * buf_out );
8> 若没有输入参数 可将输入参数类型定义为void (将忽略void 类型的参数)。
9> 函数返回类型必须为 int,可以通过返回值判断接口函数执行情况。注意:这里的int并不是接口的返回值,而是gsoap内部的返回值。真正的返回值是result。
该工具是可以根据输入的wsdl或XSD或URL,产生相应的C/C++形式的.h头文件,供soapcpp2使用。也就是从网络(本地)获取.wsdl文件生成头文件,soapcpp2根据生成的头文件生成代码框架。
用法:wsdl2h [参数] 头文件名 WSDL文件名或URL(资源地址)
参数:
选项 | 描述 |
-a | 对匿名类型,产生基于顺序号的结构体名称 |
-c | 生成C代码 |
-f | 对schema扩展,产生flat C++类 |
-g | 产生全局的元素声明 |
-h | 显示帮助信息 |
-I path | 包含文件时指明路径,相当于#import |
-j | 不产生 SOAP_ENV__Header 和SOAP_ENV__Detail 定义 |
-k | 不产生 SOAP_ENV__Header mustUnderstand qualifiers |
-l | 在输出中包含license信息 |
-m | 用 xsd.h 模块来引入类型信息 |
-N name | 用name 来指定服务命名空间的前缀。 |
-n name | 用name 作为命名空间的前缀,取代缺省的ns |
-o file | 输出文件名 |
-q name | 所有的声明采用 name 作命名空间 |
-s | 不产生 STL代码 (即不用 std::string,std::vector) |
-t file | 使用自己指定的type map file而不是缺省的typemap.dat |
-u | 不生成 unions |
-v | 产生详细的输出信息 |
-w | always wrap response parameters in a response struct |
-y | 为structs,enums产生 typedef定义 |
-_ | 不产生 _USCORE (用UNICODE _x005f代替) |
-? | 显示帮助信息 |
-t | 指定typemap文件,默认为typemap.dat |
-e | 禁止为enum成员加上命名空间前缀 |
根据头文件生成特定的代码框架。soapcpp2.exe可以带参数执行,具体执行soapcpp2.exe -h查看。
参数:
选项 | 描述 |
-1 | Soap1.1绑定 |
-2 | SOAP1.2绑定 |
-C | 只生成客户端代码 |
-S | 只生成服务器端代码 |
-T | 生成自动测试代码 |
-L | 不生成 soapClientLib/soapServerLib |
-a | 用 SOAPAction 和WS-Addressing调用服务器端方法 |
-A | 用 SOAPAction 调用服务器端方法 |
-b | 采用char[N]这样的方式来表示string |
-c | 生成的是C代码,不是C++代码 |
-d < path > | 将代码生成在 < path >下 |
-e | 生成 SOAP RPC 样式的绑定 |
-f N | File split of N XML serializer implementations per file |
-h | 显示一个简要的用法信息 |
-i | 生成的服务代理(Proxy)类和对象 从struct soap继承而来 |
-j | 生成的服务代理类和对象包含struct soap而来(C代码的唯一选择) |
-I < path > | 包含其他文件时使用,指明 < path > (多个的话,用`:'分割),相当于#import ,该路径一般是gSOAP目录下的import目录,该目录下有一堆文件供soapcpp2生成代码时使用。 |
-n | 用于生成支持多个客户端和服务器端(具体内容参考gSOAP文档) |
-p < name > | 生成的文件前缀采用< name > ,而不是缺省的 "soap" |
-q < name > | C++代码中,所有声明的命名空间 |
-s | 生成的代码在反序列化时,严格检查XML的有效性 |
-t | 生成的代码在发送消息时,采用xsi:type方式 |
-u | 在 WSDL/schema 输出文件中不产生XML注释 |
-v | 显示版本信息 |
-w | 不生成 WSDL 和 schema 文件 |
-x | 不生成 XML 形式的传输消息文件 |
-y | 在XML 形式的传输消息文件中,包含 C/C++类型信息 |
文件 | 描述 |
soapStub.h | 根据输入的.h文件生成的数据定义文件,一般我们不直接引用它。 |
soapH.h soapC.cpp | 客户端和服务器端应包含该头文件,它包含了soapStub.h。针对soapStub.h中的数据类型,cpp文件实现了序列化、反序列化方法。 |
soapXYZProxy.h soapXYZProxy.cpp | 这两个文件用于客户端,是客户端调用webservice的框架文件,我们的代码主要在此实现或从它继承。 |
soapXYZService.h soapXYZService.cpp | 这两个文件用于服务器端,是服务器端实现webservice的框架文件,我们的代码主要在此实现或从它继承。 |
.xsd | 传输消息的schema,,我们可以看看是否满足我们的协议格式(如果有此要求) |
.wsdl | 接口定义文件 |
.xml | 满足webservice定义的例子message,即实际的传输消息,我们可以看看是否满足我们的协议格式(如果有此要求)。 |
.nsmap | 命名空间的定义(需添加到服务端与客户端代码中)。 |
soapClient.cpp | 客户端代码框架,好像客户端也没有用到该文件。 |
soapClientLib.cpp | 客户端代码框架(一般不使用) |
soapServerLib.cpp | 服务端代码框架(一般不使用) |
l API接口固定,不关心底层的通讯,将SOAP作为应用层协议。
当我们拥有头文件(或者自己编写头文件时候,常见用于服务端)
l 通讯协议固定(当然需要基于XML的)或只有wsdl,将SOAP作为“传输层”协议
我们能够获取到wsdl文件时候(常见用于客户端)
gsoap传输中文
1. 设置gsoap为utf-8传输数据(我使用utf-8编码格式来支持汉字的传输。)
soap_set_mode( &SmsWBS_soap, SOAP_C_UTFSTRING ); //设置编码
SmsWBS_soap.mode|=SOAP_C_UTFSTRING;
2. 使用下面的函数转换我们的传输内容,即将我们的数据转成UTF-8编码:
int conv_charset( const char *dest, const char *src, char *input, size_t ilen,char *output, size_t olen )
{
int convlen = olen;
iconv_t conv = iconv_open( dest, src );
if( conv == (iconv_t) -1 )
return -1;
memset( output, 0, olen );
if( iconv( conv, &input, &ilen, &output, &olen ) )
{
iconv_close(conv);
return -1;
}
iconv_close(conv);
return convlen-olen;
}
例子:conv_charset( "UTF-8", "GBK", "微软.linxr", strlen("微软.linxr"), buf_out->name,100 );
//编码转换函数
int code_convert(char *from_charset,char *to_charset,char *inbuf,int inlen,char *outbuf,int outlen)
{
iconv_t cd;
int rc;
char **pin = &inbuf;
char **pout = &outbuf;
cd = iconv_open(to_charset,from_charset);
if (cd==0) return -1;
memset(outbuf,0,outlen);
if (iconv(cd,pin,&inlen,pout,&outlen)==-1) return -1;
iconv_close(cd);
return 0;
}
//UNICODE 2 GB2312
int u2g(char *inbuf,int inlen,char *outbuf,int outlen)
{
return code_convert("utf-8","gb2312",inbuf,inlen,outbuf,outlen);
}
//GB2312 2 UNICODE
int g2u(char *inbuf,size_t inlen,char *outbuf,size_t outlen)
{
return code_convert("gb2312","utf-8",inbuf,inlen,outbuf,outlen);
}
Function | Description |
soap_init(struct soap *soap) | 初始化运行的环境 |
soap_init1(struct soap *soap, soap_mode iomode) | 初始化运行的环境并设置模式 |
soap_init2(struct soap *soap, soap_mode imode, soap_mode omode) | 初始化运行的环境并设置模式 |
struct soap *soap_new() | 返回一个初始化过的soap指针 |
struct soap *soap_new1(soap_mode iomode) | 返回一个初始化并设置模式后的soap指针 |
struct soap *soap_new2(soap_mode imode, soap_mode omode) | 返回一个初始化并设置模式后的soap指针 |
struct soap *soap_copy(struct soap *soap) | 拷贝一个soap |
Function | Description |
soap_destroy(struct soap *soap) | 释放所有动态分配的C++类,必须在soap_end()之前调用。 |
soap_end(struct soap *soap) | 释放所有存储临时数据和反序列化数据中除类之外的空间(soap_malloc的数据也属于反序列化数据)。 |
soap_done(struct soap *soap) | Detach soap结构(即初始化化soap结构) |
soap_free(struct soap *soap) | Detach 且释放soap结构 |
例如:
#include"soapH.h" // include all interfaces (library and generated)
#include"calc.nsmap" // import the generated namespace mapping table
int main()
{
double sum;
//初始化
struct soap soap; // the gSOAP runtime context
soap_init(&soap); // initialize the context (only once!)
//方法调用
if (soap_call_c__add(&soap, NULL, NULL, 1.0, 2.0, &sum) == SOAP_OK)
std::cout << "Sum = " << sum << std::endl;
else // an error occurred
soap_print_fault(&soap, stderr); // display the SOAP fault message on the stderr stream
//释放资源
soap_destroy(&soap); // delete deserialized class instances (for C++)
soap_end(&soap); // remove deserialized data and clean up
soap_done(&soap); // detach the gSOAP context
return 0;
}
要能被其他人引用,别人需要获得我们的wsdl文件,获取到webServer后就会获取到我们的webServer方法。
假定我们定义:struct soap Server;
其实就是实现: Server.fget 的函数指针
int http_get(struct soap *soap)
{
FILE*fd = NULL;
char *s = strchr( soap->path, '?' );
if( !s || strcmp( s, "?wsdl" ) )
{
return SOAP_GET_METHOD;
}
fd = fopen(".\\Server.wsdl", "rb"); //open WSDL file to copy
if (!fd)
{
return 404; //return HTTP not found error
}
soap->http_content = "text/xml"; //HTTP header with text /xml content
soap_response(soap,SOAP_FILE);
for(;;)
{
size_t r = fread(soap->tmpbuf,1, sizeof(soap->tmpbuf), fd);
if (!r)
{
break;
}
if (soap_send_raw(soap, soap->tmpbuf, r))
{
break; //cannot send, but little we can do about that
}
}
fclose(fd);
soap_end_send(soap);
return SOAP_OK;
}
在dos界面下键入:
wsdl2h -c -s -o F:\学习资料\gsoap\demo\general\calc.h http://www.genivia.com/calc.wsdl
说明:
wsdl2h可以加上完整路径,本机由于加入到了环境变量,所以无需绝对路径。
在F:\学习资料\gsoap\demo\general\路径下将会生成calc.h头文件。
头文件里面包含信息:
//gsoap ns2 service name: calc
//gsoap ns2 service type: calcPortType
//gsoap ns2 service port: http://websrv.cs.fsu.edu/~engelen/calcserver.cgi
//gsoap ns2 service namespace: urn:calc
//gsoap ns2 service transport: http://schemas.xmlsoap.org/soap/http
//gsoap ns2 schema namespace: urn:calc
//gsoap ns2 schema form: unqualified
//gsoap ns2 service method-protocol: add SOAP
//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 ""
//gsoap ns2 service method-output-action: add Response
自定义信息:
//gsoap ns service name: Test
//gsoap ns service namespace: http://202.115.52.165:4567/Test.wsdl
//gsoap ns service location: http://202.115.52.165:4567
//gsoap ns service executable: Test.cgi
//gsoap ns service encoding: encoded
//gsoap ns schema namespace: urn:Test
int ns2__add(double a,double b,double *result);
int ns2__sub(double a,double b,double *result);
int ns2__mul(double a,double b,double *result);
int ns2__div(double a,double b,double *result);
int ns2__pow(double a,double b,double *result);
在dos界面键入:
soapcpp2 -2 -l -d F:\学习资料\gsoap\demo\general\ F:\学习资料\gsoap\demo\general\calc.h
说明:
-2 用soap2绑定 –l 不生成lib文件 –d 输出路径 最后是你的头文件路径
步骤:
初始化soap
绑定IP和端口
等待连接
处理请求
释放资源
还需要实现自己发布方法的逻辑,在soapStub.h文件里面有包含了方法名。一般由:SOAP_FMAC5 int SOAP_FMAC6打头。
创建soap,并初始化。
调用webserver方法
最后释放资源
struct soap add_soap;
soap_init(&add_soap);
int result = -1;
char* server = "http://202.115.52.176:4567";
double a = 1.2;
double b = 1.3;
double res=0;
soap_call_ns2__add(&add_soap, server, "", a, b, &res);
if (add_soap.error)
{
std::cout << "soap error:" << add_soap.error << "," << *soap_faultcode(&add_soap) << "," << *soap_faultstring(&add_soap) << std::endl;
res = add_soap.error;
return 0;
}
std::cout << a << "+" << b << "=" <<res<< std::endl;
soap_end(&add_soap);
代码工程链接:http://download.csdn.net/detail/hl2015222050145/9622515
gsoap学习心得体会:
1、gsoap即作客户端又作服务端的时候服务端头文件得加命名空间。不然会重定义
2、注意:此过程中要检查生成的编译信息,确保没有警告信息出现(警告信息可能是gSoap不支持的内容,如果不去除,程序运行故障很难找到根本原因),另不能只看编译结果成功就认为是正确的,存在警告信息也显示为编译成功!!
3、自己编写.h文件名的时候不要出现什么gsoap名字,因为gsoap的soapcpp2工具生成的文件会自动加上soap.否则很丑。
4、注意代码中的网址如http://127.0.0.1:8086,其中的http后只有一个冒号。
5、 class SOAP_CMAC ServerDemoService : public soap //soap 只是一个结构体不是一个类
6、//system("pause");//好像加与不加并没有什么区别
注:
由于笔者水平有限,若博文中存在错误或不周之处,还望各位大牛多多指教,有新的经验的时候也会及时更新本博文,谢谢!
本篇博文为自己学习过程的心得笔记,如有内容雷同,还请见谅,谢谢!