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++風格的.h文件。
- soapcpp2.exe的作用是根據頭檔自動生成調用遠端 SOAP服務的用戶端代碼(稱為存根:Stub)和提供SOAP服務的框架代碼(稱為框架:Skeleton),另外它也能從.h檔生成WSDL檔。
gsoap\stdsoap2.cpp則是gSOAP的核心代碼,要使用gSOAP只要在項目裏包含這個檔以及由soapcpp2.exe生成的代碼即可。另外還有個stdsoap2.c,內容與stdsoap2.cpp一 模一樣,用於純C項目。
gSOAP兩大工具的用法
從WSDL中產生.h檔:
用法:
wsdl2h -o [.h檔案名 WSDL檔案名或URL]
wsdl2h常用選項:
- -o 檔案名,指定輸出頭檔
- -n 名空間首碼 代替默認的ns
- -c 產生純C代碼,否則是C++代碼
- -s 不要使用STL代碼
- -t 檔案名,指定type map檔,默認為typemap.dat
- -e 禁止為enum成員加上名空間首碼
xsd__string = | std::wstring | wchar_t*
那麼SOAP/XML中的string將轉換成std::wstring或wchar_t*,這樣能更好地支持中文。
例如:
wsdl2h -o ayandy.h \從http://www.ayandy.com/Service.asmx?WSDL 生成ayandy.h檔,名空間為ay,使用wsmap.dat指定的轉換規則。
-n ay -t wsmap.dat \
http://www.ayandy.com/Service.asmx?WSDL
wsdl2h生成的頭檔裏的變數、類型等名稱的前面都會加上名空間首碼,以兩個下劃線分隔。如上面的命令生成的頭檔,有這樣的定義:
class ay1__ArrayOfString; enum ay1__theDayFlagEnum { ay1__theDayFlagEnum__Today, ay1__theDayFlagEnum__Tomorrow, ay1__theDayFlagEnum__theDayafterTomorrow, };
前面的ayandy1__的是名空間首碼,用以防止名稱衝突。 wsdl2h的-n選項可以改變這個名空間首碼(默認為ns)。對於枚舉ay1__theDayFlagEnum內 的成員,如果嫌它太長的話,可以用-e命令選項禁止加入名空間首碼。
從.h檔生成存根(stub)和框架(Skeleton)原始檔案
編寫SOAP程式除了頭檔是不夠的,還要有連接、通信、XML解析、序列/反序列化等工作。gSOAP提供的socapcpp2.exe就 是用於從頭檔中生成這些代碼的,我們只要關心真正的業務邏輯就行了。
用法
soapcpp2 .h文件
例如:
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常用選項
- -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 根據城市名稱獲得天 氣情況。
現在,我們編寫一個用戶端去調用getWeatherbyCityName來 取得天氣情況
- 從WSDL得到頭文件:
wsdl2h -o ayandy.h http://www.ayandy.com/Service.asmx?WSDL - 從頭檔得到存根(Stub)原始檔案:
soapcpp2 -i -C -x ayandy.h -ID:\gsoap-2.7\gsoap\import
命令選項注釋
- -i 直接使用C++包裝類
- -x 不要生成一堆看了就噁心的xml
- -C 只生成客戶端相關代碼
- -I 指定import路徑
- 建立新專案:
把gsoap庫裏的stdsoap2.cpp檔,以及上一步生成的soapServiceSoapProxy.cpp和soapC.cpp都加入到項 目。設置加入的這三個檔為不使用預編譯頭。 - 編寫代碼:
由於參數及回傳的資料都是中文,所有讓gSOAP使用UTF8方式傳送以防止亂碼。
#include <iostream> #include <string> #include "soapServiceSoapProxy.h" #include "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轉換回來。
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伺服器
現在,我們自己動手寫一個天氣預報服務,當然,是亂報的啦,呵呵。
- 這次,我們嘗試使用寬字元集的方式來支援中文:
寫一個wsmap.dat文件,裏面寫上:xsd__string = | std::wstring | std::wstring* - 從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 枚舉成員不要有長長的名空間首碼
- 從頭檔生成伺服器框架代碼:
soapcpp2 ayandy.h -i -x -S -I D:\Code\libs\gsoap-2.7\gsoap\import
命令選項注釋:
- -S 僅生成伺服器框架代碼
- 新建項目:
把gsoap庫裏的stdsoap2.cpp檔,以及上一步生成的soapServiceSoapService.cpp和soapC.cpp都加入到 項目。
設置加入的這三個檔為不使用預編譯頭。 - 編寫代碼:
打開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表示只生成一個,否則生成一個指定數目的陣列。
0 意見:
張貼留言