用gSOAP开发Web Service程序

179 篇文章 2 订阅
120 篇文章 0 订阅

用gSOAP开发Web Service程序

2009-07-23 20:25:56 作者:毛毛 来源:www.cppprog.com 浏览次数:14604 网友评论 15

gSOAP是一个绑定SOAP/XML到C/C++语言的工具,使用它可以简单快速地开发出 SOAP/XML的服务器端和客户端。由于gSOAP具有相当不错的兼容性,通过gSOAP,我们就可以调用由 Java, .Net, Delhpi, PHP等语言开发的SOAP服务,或者向它们提供SOAP服务。

gSOAP是一个绑定SOAP/XML到C/C++语言的工具,使用它可以简单快速地开发出SOAP/XML的服务器端和客户端。由于 gSOAP具 有相当不错的兼容性,通过gSOAP,我们就可以调用由Java, .Net, Delhpi, PHP等语言开发的SOAP服务,或者向它们提供SOAP服务。

gSOAP的主页是:

http://sourceforge.net/projects/gsoap2

下载解压后,可以在gsoap/bin/win32 里 找到wsdl2h.exe和soapcpp2.exe(另外还有linux和mac版本)。

  • wsdl2h.exe 的作用是根据WSDL生成C/C++风格的头 文件
  • soapcpp2.exe 的作用是根据头文件自动生成调用远程 SOAP服务的客户端代码(称为存根:Stub)和提供SOAP服务的框架代码(称为框架:Skeleton),另外它也能从头文件生成WSDL文件。

gsoap/stdsoap2.cpp 则是gSOAP的核心代码,要使用 gSOAP只要在项目里包含这个文件以及由soapcpp2.exe生成的代码即可。另外还有个stdsoap2.c,内容与stdsoap2.cpp一 模一样,用于纯C项目。

gSOAP两大工具的用法

从WSDL中产生头文件

用法:
wsdl2h -o 头文件名 WSDL文件名或URL
wsdl2h常用选项
  • -o 文件名,指定输出头文件
  • -n 名空间前缀 代替默认的ns
  • -c 产生纯C代码,否则是C++代码
  • -s 不要使用STL代码
  • -t 文件名,指定type map文件,默认为typemap.dat
  • -e 禁止为enum成员加上名空间前缀

type map文件用于指定SOAP/XML中的类型与C/C++之间的转换规则,比如在wsmap.dat里写

xsd__string = | std::wstring | wchar_t*

那么SOAP/XML中的string将转换成std::wstring或wchar_t*,这样能更好地支持中文。

例:

wsdl2h -o ayandy.h /
 -n ay -t wsmap.dat /
 http://www.ayandy.com/Service.asmx?WSDL

http://www.ayandy.com/Service.asmx?WSDL 生成ayandy.h文件,名空间为ay,使用wsmap.dat指定的转换规则。

wsdl2h生成的头文件里的变量、类型等名称的前面都会加上名空间前缀,以两个下划线分隔。如上面的命令生成的头文件,有这样的定 义:

  1. class  ay1__ArrayOfString;
  2. enum  ay1__theDayFlagEnum
  3. {
  4.  ay1__theDayFlagEnum__Today,
  5.  ay1__theDayFlagEnum__Tomorrow,
  6.  ay1__theDayFlagEnum__theDayafterTomorrow,
  7. };

前面的ayandy1__ 的是名空间前缀,用以防止名称冲突。 wsdl2h的-n 选项可以改变这个名空间前缀(默认为ns )。对于枚举ay1__theDayFlagEnum 内 的成员,如果嫌它太长的话,可以用-e 命令选项禁止加入名空间前缀。

从头文件生成存根(stub)和框架(Skeleton)源文件

编写SOAP程序除了头文件是不够的,还要有连接、通信、XML解析、序列/反序列化等工作。gSOAP提供的socapcpp2.exe 就 是用于从头文件中生成这些代码的,我们只要关心真正的业务逻辑就行了。

用法
soapcpp2 头文件
例:
soapcpp2 ayandy.h

将生成下面这些文件

  • soapStub.h     // soap的存根文件,定义了ayandy.h里对应的远程调用模型
  • soapC.c soapH.h   // soap的序列和反序列代码,它已经包含了soapStub.h,服务器端与客户端都要包含它
  • soapClient.c soapClientLib.c // 客户端代码,soapClientLib.c文件则只是简单地包含soapClient.c和soapC.c
  • soapServer.c soapServerLib.c // 服务器端代码,soapServerLib.c文件则只是简单地包含soapServer.c和soapC.c
  • ServiceSoap.nsmap ServiceSoap12.nsmap // 名空间定义,服务器端与客户端都要包含它
  • soapServiceSoapProxy.h soapServiceSoap12Proxy.h // 客户端的C++简单包装(如果头文件是纯C代码,这两个文件就不会生成)

综上所述

  • 如果编写服务器端,项目里应该加入soapServerLib.c,代码里包含头文件soapH.h
  • 如果编写客户端,项目里应该加入soapClientLib.c,代码里包含头文件SoapH.h(或xxxxProxy.h)
  • 当然,还要加入gsoap库里的stdsoap2.cpp文件(如果是写C代码,则加入stdsoap2.c)

如果看到soapcpp2提示:”Critical error: #import: Cannot open file "stlvector.h" for reading. “, 那是因为我们的头文件使用了STL(wsdl2h 没用-s选项 ),这时要使用-I 选项指定gSOAP的 import文件路径,这个路径是"$gsoap/gsoap/import ":

soapcpp2 ayandy.h -I D:/gsoap-2.7/gsoap/import
soapcpp2常用选项
  • -C 仅生成客户端代码
  • -S 仅生成服务器端代码
  • -L 不要产生soapClientLib.c和soapServerLib.c文件
  • -c 产生纯C代码,否则是C++代码(与头文件有关)
  • -I 指定import路径(见上文)
  • -x 不要产生XML示例文件
  • -i 生成C++包装,客户端为xxxxProxy.h(.cpp),服务器端为xxxxService.h(.cpp)

编写SOAP客户端

下面将演示使用gSOAP到网上取得天气预报,互联网上有不少网站提供SOAP服务,比如Google提供的搜索API(现在已不再提 供新的License Key了),不少博客提供的API等。这里介绍一个提供天气预报服务的SOAP服务,地址是http://www.ayandy.com

它提供了三个函数
  • getSupportCity 查询本天气WebService支持的城市信息。
  • getSupportProvince 查询本天气 WebService支持的省份信息。
  • getWeatherbyCityName 根据城市名称获得天 气情况。

它的WSDL地址是http://www.ayandy.com/Service.asmx?WSDL

现在,我们编写一个客户端去调用getWeatherbyCityName 来 取得天气情况

1. 从WSDL得到头文件

wsdl2h -o ayandy.h http://www.ayandy.com/Service.asmx?WSDL

2. 从头文件得到存根(Stub)源文件

soapcpp2 -i -C -x ayandy.h -ID:/gsoap-2.7/gsoap/import

命令选项注释:

 -i 直接使用C++包装类
 -x 不要生成一堆看了就恶心的xml
 -C 只生成客户端相关代码
 -I 指定import路径
3. 建立新项目

把gsoap库里的stdsoap2.cpp文件,以及上一步生成的soapServiceSoapProxy.cpp和soapC.cpp都加入到项 目。

设置加入的这三个文件为不使用预编译头。

4. 编写代码

由于参数及回传的数据都是中文,所有让gSOAP使用UTF8方式传送以防止乱码。

  1. #include <iostream>
  2. #include <string>
  3. #include "soapServiceSoapProxy.h"
  4. #include& nbsp;"ServiceSoap.nsmap" //表忘了名空间定义
  5.  
  6. using   namespace  std;
  7.  
  8. // 宽 字符转UTF8
  9. string EncodeUtf8(wstring in)
  10. {
  11.     string s(in.length()*3+1,' ' );
  12.     size_t  len = ::WideCharToMultiByte(CP_UTF8, 0,
  13.             in.c_str(), in.length(),
  14.             &s[0], s.length(),
  15.             NULL, NULL);
  16.     s.resize(len);
  17.     return  s;
  18. }
  19.  
  20. // UTF8 转宽字符
  21. wstring DecodeUtf8(string in)
  22. {
  23.     wstring s(in.length(), _T(' ' ));
  24.     size_t  len = ::MultiByteToWideChar(CP_UTF8, 0,
  25.             in.c_str(), in.length(),
  26.             &s[0], s.length());
  27.     s.resize(len);
  28.     return  s;
  29. }
  30.  
  31. int  main( int  argc,  char * argv[])
  32. {
  33.     ServiceSoapProxy gs(SOAP_C_UTFSTRING);
  34.  
  35.     _ns1__getWeatherbyCityName cityname;
  36.     _ns1__getWeatherbyCityNameResponse resp;
  37.  
  38.     string strCityName = EncodeUtf8(L"苏州" );
  39.     cityname.theCityName = &strCityName;
  40.     cityname.theDayFlag = ns1__theDayFlagEnum__Tomorrow;
  41.  
  42.     if (gs.getWeatherbyCityName(&cityname, &resp) == SOAP_OK)
  43.     {
  44.         ns1__ArrayOfString *aos = resp.getWeatherbyCityNameResult;
  45.         wcout.imbue( std::locale("chs" ) );  //指定输出为中文
  46.         for (vector<string>::iterator
  47.                 itr=aos->string.begin(), itr_end = aos->string.end();
  48.                 itr!=itr_end; ++itr)
  49.             wcout << DecodeUtf8(*itr) << endl;
  50.  
  51.     }
  52.  
  53.     return  0;
  54. }

上面的代码花了一半在UTF8编码转换上,如果参数里没有中文的话,代码会简化很多:

  1. ServiceSoapProxy gs;
  2. _ns1__getWeatherbyCityName cityname;
  3. _ns1__getWeatherbyCityNameResponse resp;
  4. string strCityName("苏州" );
  5. cityname.theCityName = &strCityName;
  6. cityname.theDayFlag = ns1__theDayFlagEnum__Tomorrow;
  7.  
  8. if (gs.getWeatherbyCityName(&cityname, &resp) == SOAP_OK)
  9. {
  10.     ns1__ArrayOfString *aos = resp.getWeatherbyCityNameResult;
  11.     for (vector<string>::iterator
  12.             itr=aos->string.begin(), itr_end = aos->string.end();
  13.             itr!=itr_end; ++itr)
  14.         cout << *itr << endl;
  15. }

但是这个代码应用到中文字符串时,会发现返回的是一堆乱码,gSOAP有两种方式支持它:

  1. 使用宽字符集,如用前文演示的type map文件来转换字符串为std::wstring。
  2. 使用UTF8传送字符串,这个例子就是使用的这个方式:首先,定义ServiceSoapProxy gs的传送模式为SOAP_C_UTFSTRING;然后输入时把字符串转换成UTF8,得到输出时把UTF8转换回来。

使用UTF8时还要注意一点,如果使用纯C调用,那么应该这样设置UTF8调用:

  1. soap sp;
  2. soap_init(&sp);
  3. soap_set_mode(&sp, SOAP_C_UTFSTRING);
  4. sp.mode |= SOAP_C_UTFSTRING; //关键

也许是gSOAP的bug吧,soap_set_mode 只 设置了sp.imode和sp.omode两个成员,却没有设置sp.mode。跟踪代码可以发现从服务器传回数据后,gSOAP是根据sp.mode来 决定是否使用UTF8转换的

 

编写SOAP服务器

现在,我们自己动手写一个天气预报服务,当然,是乱报的啦,呵呵。

1.这次,我们尝试使用宽字符集的方式来支持中文

写一个wsmap.dat文件,里面写上:xsd__string = | std::wstring | std::wstring*

2.从WSDL生成头文件

wsdl2h.exe -o ayandy.h -t wsmap.dat -e http://www.ayandy.com/Service.asmx?WSDL

命令选项注释:

  •  -o ayandy.h 生成ayandy.h头文件
  •  -t wsmap.dat 根据wsmap.dat规则转换数据类型
  •  -e 枚举成员不要有长长的名空间前缀
3.从头文件生成服务器框架代码

soapcpp2 ayandy.h -i -x -S -I D:/Code/libs/gsoap-2.7/gsoap/import

命令选项注释

  • -S 仅生成服务器框架代码
4.新建项目

把gsoap库里的stdsoap2.cpp文件,以及上一步生成的soapServiceSoapService.cpp和soapC.cpp都加入到 项目。

设置加入的这三个文件为不使用预编译头。

5.编写代码

打开soapcpp2生成的soapServiceSoapService.h文件,在ServiceSoapService类定义里 会看到这样几行字:

  1. ///
  2.  /// Service operations (you should define these):
  3.  ///

它后面的几个方法是要我们自己实现它的,先看代码吧:

  1. #include "soapServiceSoapService.h"
  2. #include "ServiceSoap.nsmap"
  3.  
  4. /// Web service operation 'getWeatherbyCityName' (returns error code or SOAP_OK)
  5. int  ServiceSoapService::getWeatherbyCityName(
  6.         _ns1__getWeatherbyCityName *ns1__getWeatherbyCityName,
  7.         _ns1__getWeatherbyCityNameResponse *ns1__getWeatherbyCityNameResponse)
  8. {
  9.     if (*(ns1__getWeatherbyCityName->theCityName) != L "苏州"return  SOAP_USER_ERROR;
  10.  
  11.     ns1__ArrayOfString * aos = soap_new_ns1__ArrayOfString(this , -1);
  12.  
  13.     aos->string.push_back( std::wstring() ); //第0个空着
  14.     if (ns1__getWeatherbyCityName->theDayFlag != Tomorrow)
  15.     {
  16.         aos->string.push_back( L"我只知道明天天气,其它的不要问我!"  );
  17.     }
  18.     else
  19.     {
  20.         aos->string.push_back( L"有日食,不过下大雨,哈哈,气死你!"  );
  21.         aos->string.push_back( L"下雨当然有风啦,多大我也不知道"  );
  22.     }
  23.     ns1__getWeatherbyCityNameResponse->getWeatherbyCityNameResult = aos;
  24.     return  SOAP_OK;
  25. }
  26.  
  27. /// Web service operation 'getSupportProvince' (returns error code or SOAP_OK)
  28. int  ServiceSoapService::getSupportProvince(
  29.         _ns1__getSupportProvince *ns1__getSupportProvince,
  30.         _ns1__getSupportProvinceResponse *ns1__getSupportProvinceResponse)
  31. {
  32.     return  SOAP_OK;
  33. }
  34.  
  35. /// Web service operation 'getSupportCity' (returns error code or SOAP_OK)
  36. int  ServiceSoapService::getSupportCity(
  37.         _ns1__getSupportCity *ns1__getSupportCity,
  38.         _ns1__getSupportCityResponse *ns1__getSupportCityResponse)
  39. {
  40.     return  SOAP_OK;
  41. }
  42.  
  43.  
  44. int  main( int  argc,  char * argv[])
  45. {
  46.     ServiceSoapService sev;
  47.     return  sev.run(8888); //本机8888端口
  48. }

编译,运行,现在我们的本机8888端口开始提供天气预报的SOAP服务了。

修改之前的客户端,在main()里第一行

  1. ServiceSoapProxy gs(SOAP_C_UTFSTRING);

后面加上:

  1. gs.soap_endpoint= "http://localhost:8888" ;

运行这客户端后可以就看到我们提供的优质服务了:)

本例中getWeatherbyCityName 方 法里的ns1__getWeatherbyCityNameResponse 参 数用于返回数据,它的getWeatherbyCityNameResult 成 员是由我们来申请内存的,这个内存应该用“soap_new_ 类名 ”来取得,这些申请函数可以从soapH.h 里找到,如本例的soap_new_ns1__ArrayOfString

  • 第一个参数是soap类型,它是ServiceSoapService的父类型,也是gSOAP中最重要的类型。
  • 第二个指定申请的个数,指定为-1表示只生成一个,否则生成一个指定数目的数组。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值