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生成的頭文件裏的變量、類型等名稱的前面都會加上名空間前綴,以兩個下劃線分隔。如上面的命令生成的頭文件,有這樣的定 義:
- class ay1__ArrayOfString;
- enum ay1__theDayFlagEnum
- {
- ay1__theDayFlagEnum__Today,
- ay1__theDayFlagEnum__Tomorrow,
- ay1__theDayFlagEnum__theDayafterTomorrow,
- };
前面的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方式傳送以防止亂碼。
- #include <iostream>
- #include <string>
- #include "soapServiceSoapProxy.h"
- #include& nbsp;"ServiceSoap.nsmap" //表忘了名空間定義
- using namespace std;
- // 寬 字符轉UTF8
- string EncodeUtf8(wstring in)
- {
- string s(in.length()*3+1,' ' );
- size_t len = ::WideCharToMultiByte(CP_UTF8, 0,
- in.c_str(), in.length(),
- &s[0], s.length(),
- NULL, NULL);
- s.resize(len);
- return s;
- }
- // UTF8 轉寬字符
- wstring DecodeUtf8(string in)
- {
- wstring s(in.length(), _T(' ' ));
- size_t len = ::MultiByteToWideChar(CP_UTF8, 0,
- in.c_str(), in.length(),
- &s[0], s.length());
- s.resize(len);
- return s;
- }
- int main( int argc, char * argv[])
- {
- ServiceSoapProxy gs(SOAP_C_UTFSTRING);
- _ns1__getWeatherbyCityName cityname;
- _ns1__getWeatherbyCityNameResponse resp;
- string strCityName = EncodeUtf8(L"蘇州" );
- cityname.theCityName = &strCityName;
- cityname.theDayFlag = ns1__theDayFlagEnum__Tomorrow;
- if (gs.getWeatherbyCityName(&cityname, &resp) == SOAP_OK)
- {
- ns1__ArrayOfString *aos = resp.getWeatherbyCityNameResult;
- wcout.imbue( std::locale("chs" ) ); //指定輸出爲中文
- for (vector<string>::iterator
- itr=aos->string.begin(), itr_end = aos->string.end();
- itr!=itr_end; ++itr)
- wcout << DecodeUtf8(*itr) << endl;
- }
- return 0;
- }
上面的代碼花了一半在UTF8編碼轉換上,如果參數裏沒有中文的話,代碼會簡化很多:
- ServiceSoapProxy gs;
- _ns1__getWeatherbyCityName cityname;
- _ns1__getWeatherbyCityNameResponse resp;
- string strCityName("蘇州" );
- cityname.theCityName = &strCityName;
- cityname.theDayFlag = ns1__theDayFlagEnum__Tomorrow;
- if (gs.getWeatherbyCityName(&cityname, &resp) == SOAP_OK)
- {
- ns1__ArrayOfString *aos = resp.getWeatherbyCityNameResult;
- for (vector<string>::iterator
- itr=aos->string.begin(), itr_end = aos->string.end();
- itr!=itr_end; ++itr)
- cout << *itr << endl;
- }
但是這個代碼應用到中文字符串時,會發現返回的是一堆亂碼,gSOAP有兩種方式支持它:
- 使用寬字符集,如用前文演示的type map文件來轉換字符串爲std::wstring。
- 使用UTF8傳送字符串,這個例子就是使用的這個方式:首先,定義ServiceSoapProxy gs的傳送模式爲SOAP_C_UTFSTRING;然後輸入時把字符串轉換成UTF8,得到輸出時把UTF8轉換回來。
使用UTF8時還要注意一點,如果使用純C調用,那麼應該這樣設置UTF8調用:
- soap sp;
- soap_init(&sp);
- soap_set_mode(&sp, SOAP_C_UTFSTRING);
- 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類定義裏 會看到這樣幾行字:
- ///
- /// Service operations (you should define these):
- ///
它後面的幾個方法是要我們自己實現它的,先看代碼吧:
- #include "soapServiceSoapService.h"
- #include "ServiceSoap.nsmap"
- /// Web service operation 'getWeatherbyCityName' (returns error code or SOAP_OK)
- int ServiceSoapService::getWeatherbyCityName(
- _ns1__getWeatherbyCityName *ns1__getWeatherbyCityName,
- _ns1__getWeatherbyCityNameResponse *ns1__getWeatherbyCityNameResponse)
- {
- if (*(ns1__getWeatherbyCityName->theCityName) != L "蘇州" ) return SOAP_USER_ERROR;
- ns1__ArrayOfString * aos = soap_new_ns1__ArrayOfString(this , -1);
- aos->string.push_back( std::wstring() ); //第0個空着
- if (ns1__getWeatherbyCityName->theDayFlag != Tomorrow)
- {
- aos->string.push_back( L"我只知道明天天氣,其它的不要問我!" );
- }
- else
- {
- aos->string.push_back( L"有日食,不過下大雨,哈哈,氣死你!" );
- aos->string.push_back( L"下雨當然有風啦,多大我也不知道" );
- }
- ns1__getWeatherbyCityNameResponse->getWeatherbyCityNameResult = aos;
- return SOAP_OK;
- }
- /// Web service operation 'getSupportProvince' (returns error code or SOAP_OK)
- int ServiceSoapService::getSupportProvince(
- _ns1__getSupportProvince *ns1__getSupportProvince,
- _ns1__getSupportProvinceResponse *ns1__getSupportProvinceResponse)
- {
- return SOAP_OK;
- }
- /// Web service operation 'getSupportCity' (returns error code or SOAP_OK)
- int ServiceSoapService::getSupportCity(
- _ns1__getSupportCity *ns1__getSupportCity,
- _ns1__getSupportCityResponse *ns1__getSupportCityResponse)
- {
- return SOAP_OK;
- }
- int main( int argc, char * argv[])
- {
- ServiceSoapService sev;
- return sev.run(8888); //本機8888端口
- }
編譯,運行,現在我們的本機8888端口開始提供天氣預報的SOAP服務了。
修改之前的客戶端,在main()裏第一行
- ServiceSoapProxy gs(SOAP_C_UTFSTRING);
後面加上:
- gs.soap_endpoint= "http://localhost:8888" ;
運行這客戶端後可以就看到我們提供的優質服務了:)
本例中getWeatherbyCityName 方 法裏的ns1__getWeatherbyCityNameResponse 參 數用於返回數據,它的getWeatherbyCityNameResult 成 員是由我們來申請內存的,這個內存應該用“soap_new_ 類名 ”來取得,這些申請函數可以從soapH.h 裏找到,如本例的soap_new_ns1__ArrayOfString 。
- 第一個參數是soap類型,它是ServiceSoapService的父類型,也是gSOAP中最重要的類型。
- 第二個指定申請的個數,指定爲-1表示只生成一個,否則生成一個指定數目的數組。