用gSOAP開發Web Service程序

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文件名或URLwsdl2h常用選項

  • -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\importsoapcpp2常用選項

  • -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表示只生成一個,否則生成一個指定數目的數組。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章