用gSOAP開發Web Service程序

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. using namespace std;
  6. // 寬 字符轉UTF8
  7. string EncodeUtf8(wstring in)
  8. {
  9.      string s(in.length()*3+1,' ' );
  10.     size_t len = ::WideCharToMultiByte(CP_UTF8, 0,
  11.              in.c_str(), in.length(),
  12.              &s[0], s.length(),
  13.              NULL, NULL);
  14.      s.resize(len);
  15.     return s;
  16. }
  17. // UTF8 轉寬字符
  18. wstring DecodeUtf8(string in)
  19. {
  20.      wstring s(in.length(), _T(' ' ));
  21.     size_t len = ::MultiByteToWideChar(CP_UTF8, 0,
  22.              in.c_str(), in.length(),
  23.              &s[0], s.length());
  24.      s.resize(len);
  25.     return s;
  26. }
  27. int main( int argc, char * argv[])
  28. {
  29.      ServiceSoapProxy gs(SOAP_C_UTFSTRING);
  30.      _ns1__getWeatherbyCityName cityname;
  31.      _ns1__getWeatherbyCityNameResponse resp;
  32.      string strCityName = EncodeUtf8(L"蘇州" );
  33.      cityname.theCityName = &strCityName;
  34.      cityname.theDayFlag = ns1__theDayFlagEnum__Tomorrow;
  35.     if (gs.getWeatherbyCityName(&cityname, &resp) == SOAP_OK)
  36.      {
  37.          ns1__ArrayOfString *aos = resp.getWeatherbyCityNameResult;
  38.          wcout.imbue( std::locale("chs" ) ); //指定輸出爲中文
  39.         for (vector<string>::iterator
  40.                  itr=aos->string.begin(), itr_end = aos->string.end();
  41.                  itr!=itr_end; ++itr)
  42.              wcout << DecodeUtf8(*itr) << endl;
  43.      }
  44.     return 0;
  45. }

上面的代碼花了一半在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. if (gs.getWeatherbyCityName(&cityname, &resp) == SOAP_OK)
  8. {
  9.      ns1__ArrayOfString *aos = resp.getWeatherbyCityNameResult;
  10.     for (vector<string>::iterator
  11.              itr=aos->string.begin(), itr_end = aos->string.end();
  12.              itr!=itr_end; ++itr)
  13.          cout << *itr << endl;
  14. }

但是這個代碼應用到中文字符串時,會發現返回的是一堆亂碼,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. /// Web service operation 'getWeatherbyCityName' (returns error code or SOAP_OK)
  4. int ServiceSoapService::getWeatherbyCityName(
  5.          _ns1__getWeatherbyCityName *ns1__getWeatherbyCityName,
  6.          _ns1__getWeatherbyCityNameResponse *ns1__getWeatherbyCityNameResponse)
  7. {
  8.     if (*(ns1__getWeatherbyCityName->theCityName) != L "蘇州" ) return SOAP_USER_ERROR;
  9.      ns1__ArrayOfString * aos = soap_new_ns1__ArrayOfString(this , -1);
  10.      aos->string.push_back( std::wstring() ); //第0個空着
  11.     if (ns1__getWeatherbyCityName->theDayFlag != Tomorrow)
  12.      {
  13.          aos->string.push_back( L"我只知道明天天氣,其它的不要問我!" );
  14.      }
  15.     else
  16.      {
  17.          aos->string.push_back( L"有日食,不過下大雨,哈哈,氣死你!" );
  18.          aos->string.push_back( L"下雨當然有風啦,多大我也不知道" );
  19.      }
  20.      ns1__getWeatherbyCityNameResponse->getWeatherbyCityNameResult = aos;
  21.     return SOAP_OK;
  22. }
  23. /// Web service operation 'getSupportProvince' (returns error code or SOAP_OK)
  24. int ServiceSoapService::getSupportProvince(
  25.          _ns1__getSupportProvince *ns1__getSupportProvince,
  26.          _ns1__getSupportProvinceResponse *ns1__getSupportProvinceResponse)
  27. {
  28.     return SOAP_OK;
  29. }
  30. /// Web service operation 'getSupportCity' (returns error code or SOAP_OK)
  31. int ServiceSoapService::getSupportCity(
  32.          _ns1__getSupportCity *ns1__getSupportCity,
  33.          _ns1__getSupportCityResponse *ns1__getSupportCityResponse)
  34. {
  35.     return SOAP_OK;
  36. }
  37. int main( int argc, char * argv[])
  38. {
  39.      ServiceSoapService sev;
  40.     return sev.run(8888); //本機8888端口
  41. }

編譯,運行,現在我們的本機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表示只生成一個,否則生成一個指定數目的數組。
發佈了15 篇原創文章 · 獲贊 13 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章