基于C++的WebService开发库gsoap

因为项目中需要使用WebService,所以将webservice的有关东西温故了一下。以前采用C#,这几乎不成什么问题,毕竟C#与WebService几乎是同时火起来的,所以C#天然地支持webservice就不奇怪了。Java的情况大致与C#一样,C/C++就不同了,还好有gSOAP,就用一下吧。

一、gSOAP简介

gSOAP是一个开发SOAP和XML应用(它们组成了webservice)的工具,在英文中叫toolkit。它是跨平台的,webservice的客户端和服务器端,都可以用它来辅助开发。它主要的功能(特征)如下:

  • C/C++数据绑定工具,支持XML-RPCfrom/to JSON from/to C/C++ serialization
  • 支持WSDL 1.1,2.0, SOAP 1.1, 1.2
  • 支持REST HTTP(S) 1.0/1.1 operations (GET,PUT,POST etc) for XML, JSON,etc
  • 支持MIME and MTOM 附件
  • 支持IPv4,IPv6, TCP 和UDP
  • 支持CGI,FastCGI
  • 支持嵌入到Apache,IIS中发布
  • 自带了一个Web server (multithreaded, SSL, compression)用于发布
  • 可适用于WinCE, Palm, Symbian, VxWorks, Andriod, iPhone等小设备
  • ...(拣主要的,其余忽略)

二、gSOAP结构

目前gSOAP的版本是2.8.12,作者认为,gSOAP的组织结构以及使用的方便性,在开源项目中是比较好的。

在应用中,我们首先要应用它的两个工具: soapcpp2和 wsdl2h。所幸的是这个两个工具在gSOAP包中已经被编译生成(bin目录下),所以我们只要拿来用即可,gSOAP使用的方便性就体现出来了。另一个方便性是它的源文件个数较少,如果我们不去研究,少的文件个数包含在我们的工程中,也减少了维护的成本。

1.soapcpp2的用法

Soapcpp2是一个根据.h文件生成若干支持webservice的代码生成工具,生成的代码文件包括webservice客户端和服务器的实现框架,XML数据绑定等,具体说明如下:

文件

描述

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

命名空间的定义,对命名空间不敏感的,不用关注。

使用soapcpp2时,可选项如下:


选项

描述

-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

生成的服务代理类和对象从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++ 类型信息


2. wsdl2h的用法

该工具是可以根据输入的wsdl或XSD或URL,产生相应的C/C++形式的.h(不能直接引用),供soapcpp2使用。

wsdl2h主要的运行选项如下:


选项

描述

-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代替)

-?

显示帮助信息

三、用gsoap开发web service的大致思路

我们开发webservice应用,大致有两个方向:

1.  API接口固定,不关心底层的通讯,将SOAP作为应用层协议

此时,我们先定义接口,编写好.h文件,运行soapcpp2生成出相应的代码,对服务器端,修改XXXService文件,实现业务逻辑,对客户端,修改XXXProxy文件,实现业务逻辑。

2.  通讯协议固定(当然需要基于XML的)或只有wsdl,将SOAP作为“传输层”协议

此时,我们必须根据通讯协议或wsdl生成相应的C/C++类型的.h文件,如果需要我们自己编写wsdl,则需要一点其相关知识,不过我们可以用C#等生成一个简单的wsdl,照猫画虎即可。运用wsdl2h,我们可以生成.h文件,有了.h后,按上面的步骤继续。


四、应用实例

上节介绍了gSOAP的应用有两种,大部分介绍gSOAP的文章,都以第一种为主,其实第二种应用包含了第一种,所以文本只介绍第二种应用。

本文的例子中,前提是通讯协议(格式)已定,webservice名已定,我们需要自己编写xml schema及wsdl文件。

1.xml schema

 这部分内容不属于本文范围,其实,对xml schema和wsdl编写,最好的办法就是找一个接近的例子,修改一下来满足我们的要求。

2.wsdl

 一个wsdl通常由如下部分组成:

[html]  view plain  copy
  1. <wsdl:definitions>  
  2.    <wsdl:types/>  
  3.    <wsdl:message/>  
  4.    <wsdl:portType/>  
  5.    <wsdl:binding/>  
  6.    <wsdl:service/>  
  7. </wsdl:definitions>  
其中

  • types也就是通讯内容的格式(上面的xml schema),一般情况下,所有的类型最终被包含到一个复杂类型中,作为下面message中数据包引用的类型;
  • message定义了通讯的数据包(通讯一般是双向的,因此,一个调用过程有两个message定义,一般情况下用<methodname>in和<methodname>out分别表示进入和发出服务器的数据包),该数据包引用了上面types中的最终的复杂类型;
  • portType包含了若干operation(对应于代码实现中的method),定义了该operation进出的message;
  • binding是对上面operation的网络描述,也就是将operation与某一可以用soap形式进行网络访问的方式“绑定”在一起,binding指明了每个method的访问地址和方式。
  • service是对整个web服务的描述(访问地址等)。

 wsdl的定义是从小到大,一层一层的,从外部调用的角度来看,却是从大到小,由下往上理解:先有service,再有method(portType中的operation),然后有message,最后才有各种内容类型定义(schema),我们最终操作的起点与终点就是构造/解析这些类型。

3.生成.h

 由wsdl生成.h文件,使用的工具是wsdl2h,该工具的各种运行选项,见作者上篇文章。为使代码简洁,我们一般情况下需要采用如下的选项:

  • 允许stl类型(不加-s)
  • 产生typedef定义(加-y)
  • 指定自己的命名空间(加-N –n)
  • 指定自己的输出文件名(加–o)

由wsdl2h生成的.h文件其实不是一个标准的C语言头文件,它是一个gsoap能识别的中间文件。

 4.生成代码

 无论是wsdl生成的.h文件,还是自己写的一个.h文件,如果要生成相关的代码,必须使用soapcpp2,该工具的选项较多,因而生成的代码多样,一般情况,考虑如下选项:

  • 如果对自己编写的xml schema比较有把握,可以不用生成例子message文件,否则生成出来查看一下即可(-x)
  • 指定代码生成的import目录,在使用stl时需要(-I)
  • 生成的实现类到底采用继承struct soap还是包含struct soap,并无特别的差别,视个人使用习惯而定,采用继承时,代码似乎简洁一些(-i)
  • 指定输出文件名(-o)
  • 指定命名空间(-q)
  • 指定文件名前缀(-p)
  • 只生产客户端代码(-S)
  • 只生产服务器端代码(-C)

 5.编译项目

用以上两个工具,会生成若干文件,建议服务器端和客户端分开生成,这样会比较清晰。要编译这个项目,还需要两个gSOAP包中提供的两个固定(我们不要修改)的文件:stdsoap2.h  stdsoap2.c/stdsoap2.cpp(一个用于C,一个用于C++),这两个文件提供了通用的webservice实现功能。

如果以上步骤都没有错误,编译成功就是水到渠成的事情,采用VC或gcc都可以成功。

6.编写自己的实现逻辑

 服务器端:

在生成的代码中,有一个叫做<servicebindingname>service的类,该类从struct soap继承(-i)或包含一个struct soap(-j),是服务实现的框架类。此类中有一些虚函数,是我们主要关注的东西。

一般情况下,应继承该类,在继承类中,实现那几个service方法的虚函数(其它几个虚函数的作用见下节),完成业务逻辑。

 客户端:

与服务器端的情况非常类似,在生成的代码中,有一个叫做<servicebindingname>proxy的类,一般情况下,应继承该类,在继承类中,实现那几个service方法的虚函数(其它几个虚函数的作用见下节),完成调用远程方法的业务逻辑。


服务器端与客户端的接口,传入的是request的xml,传出的response的xml,客户端还有一种接口是将service方法调用重定位到其它的服务器。我们在继承类中的实现主要应该是xml的解析(对request)和xml的生成(对response),webservice业务的真正逻辑应单独放到其它文件中。


五、gsoap应用的一些讨论

 1.  内存管理

C/C++最大的麻烦,也是最大的优点是它要求用户自己管理内存。我们在实现web service方式时,同样需要考虑内存的分配与释放。

分配内存有两类:
  • 分配n个字节,采用

void*soap_malloc(struct soap *soap, size_t n) 

  • 分配某个类,采用

Class*soap_new_Class(struct soap *soap)    一个类

Class*soap_new_Class(struct soap *soap, int n)    n个类

这里的类是通讯xml中定义的元素,在response构造时,必然要创建若干此类元素。为简化类的创建,可定义如下宏:

[cpp]  view plain  copy
  1. #defineNEW_ELEMENT(classtype)     soap_new_##classtype(GetSoapStruct(),-1)  
  2.   
  3. #defineNEW_ELEMENT_X(classtype,n) soap_new_##classtype(GetSoapStruct(),n)  
其中 GetSoapSturct()是返回继承的或包含的struct soap结构,对继承方式的代码,它的定义如下:

[cpp]  view plain  copy
  1. struct soap *GetSoapStruct() { return(struct soap*)this; }  
在我们的Web方法实现中,可以随意使用上面的new方法,在每次web方法完结后,调用soap_destroy(struct soap *soap) ,它会为我们清除掉这部分内存。

 gsoap中有若干释放内存的方法,几个有用的函数(还有其它的,忽略)及其说明如下:

Function Call

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结构


上表中,动态分配的C++类,指上面用"soap_new"分配的类;临时数据是指那些在序列化/反序列化过程中创建的例如hash表等用来帮助解析、跟踪xml的数据;反序列化数据是指在接收soap过程中产生的用malloc和new分配空间存储的数据。在gsoap中,纯数据空间与类空间管理不同,采用两个方法,可以保留soap的反序列化数据(这时你需要自己释放)。

前两个函数常用于每个调用完成后,后两个函数常用于不需要这个service前。

2. 服务器端侦听的实现

我们除了要完成webservice方法的具体实现外,还必须知道如何调用这些方法,在客户端要实现webservice方法的调用,在服务器端要实现webservice的侦听。客户端远程方法调用的实现较简单,直接调用即可,服务器端就比麻烦一些,好在gsoap的文档写得比较好,各方面的内容都已涉及,所以实现的细节及相关说明,最好看看其文档,这里大致介绍一下。

服务器端的的侦听,有单线程实现和多线程实现。

单线程实现的代码如下:

[cpp]  view plain  copy
  1. int webservice_single_thread(int port)  
  2. {       
  3.      CWebService myservice;  
  4.      soap_set_imode(&myservice, SOAP_XML_TREE);  
  5.       myservice.accept_timeout = 5;  
  6.   
  7.       printf("started to binding...\n");   
  8.       if (!soap_valid_socket(myservice.bind(NULL, port, 100)))  return myservice.error;  
  9.       printf("started to serve...\n");   
  10.       unsigned int seq=0;  
  11.       do   
  12.       {   
  13.           if (!soap_valid_socket(myservice.accept()))  
  14.           {  
  15.               if( strncmp(myservice.fault->faultstring,"Timeout", 100) == 0)              //timeout occur, donot return  
  16.               {  
  17.   
  18.                   continue;  
  19.               }  
  20.               else  
  21.                 return myservice.error;    //socket错误,退出方法  
  22.           }  
  23.           printf("%d: accepted connection from IP=%d.%d.%d.%d", seq,  
  24.               (myservice.ip >> 24)&0xFF, (myservice.ip >> 16)&0xFF, (myservice.ip >> 8)&0xFF, myservice.ip&0xFF);  
  25.           int result = myservice.serve();  
  26.           if(result != SOAP_OK)   
  27.           {  
  28.               char errmsg[1024];  
  29.               soap_sprint_fault((struct soap *)&myservice,errmsg,1024);  
  30.               printf(" error occured(%d):%s\n",myservice.error,errmsg);   
  31.                           myservice.destroy();        
  32.               return myservice.error;       //web method发生错误,退出方法  
  33.           }  
  34.           myservice.destroy();  
  35.           seq++;  
  36.       }  
  37.       while(1);  
  38. }       
以上代码中,

SOAP_XML_TREE的设置是针对那些有重复xml节点却没有id属性的,即soap中的xml元素可以重复。

将侦听超时时间设为5秒,如果5秒以内我们没有收到消息,就可以有机会处理一下除侦听外的其他事情(上面代码中什么也没做)。

上面代码中,如果发生错误,将退出程序,如果不需要这种效果,可以修改代码。
如果要实现多线程侦听,思路如下:

accept()到一个客户端后,应将当前的soap保存一个备份,创建线程,在另一个线程使用这个备份soap及调用server()方法,大致的代码: 

[cpp]  view plain  copy
  1.          tsoap = soap_copy(&soap); // make a safe copy   
  2.          if (!tsoap)   
  3.             break;   
  4.          pthread_create(&tid, NULL, (void*(*)(void*))process_request, (void*)tsoap);  
  5.   
  6. //线程处理函数  
  7. void *process_request(void *soap)   
  8. {   
  9.    pthread_detach(pthread_self());   
  10.    soap_serve((struct soap*)soap);   
  11.    soap_destroy((struct soap*)soap); // dealloc C++ data   
  12.    soap_end((struct soap*)soap); // dealloc data and clean up   
  13.    soap_done((struct soap*)soap); // detach soap struct   
  14.    free(soap);   
  15.    return NULL;   
  16. }  

3. 其他特性

gsoap有许多功能,例如支持SSL,压缩,JSON等,请参阅其文档。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值