目錄 一、基本概念 1.1 關(guān)于SOAP 1.2 關(guān)于gSOAP 1.3 gSOAP編譯器(命令行工具) 1.3.1 wsdl2h 1.3.2 socapcpp2 二、gSOAP開發(fā):Web Service服務(wù)端 三、gSOAP開發(fā):Web Service客戶端 四、參考資料 一、基本概念 1.1 關(guān)于SOAP SOAP(Simple Object Access Protocol),即簡(jiǎn)單對(duì)象訪問協(xié)議,是在分布式的環(huán)境中交換數(shù)據(jù)的簡(jiǎn)單協(xié)議,以XML作為數(shù)據(jù)傳送語(yǔ)言。
看一個(gè)簡(jiǎn)單的請(qǐng)求及回復(fù)SOAP數(shù)據(jù)(真實(shí)數(shù)據(jù)): POST /wpsoap/ HTTP/1.1 Host: 127.0.0.1:10240 User-Agent: gSOAP/2.7 Content-Type: text/xml; charset=utf-8; action="" Content-Length: 480 Connection: close SOAPAction: "" <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas./soap/envelope/" xmlns:SOAP-ENC="http://schemas./soap/encoding/" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:xsd="http://www./2001/XMLSchema" xmlns:ns1="http://www./wpsoap/" xmlns:ns2="urn:nszfpt"><SOAP-ENV:Body><ns2:login><req><username>admin</username><password>3.14159</password></req></ns2:login></SOAP-ENV:Body></SOAP-ENV:Envelope> HTTP/1.1 200 OK Server: gSOAP/2.7 Content-Type: text/xml; charset=utf-8; action="" Content-Length: 555 Connection: close <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas./soap/envelope/" xmlns:SOAP-ENC="http://schemas./soap/encoding/" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:xsd="http://www./2001/XMLSchema" xmlns:wpsoap="urn:nszfpt"><SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas./soap/encoding/"><wpsoap:tagRspLogin><rsp><retCode>0</retCode><retMessage>login ok!</retMessage></rsp><session>01234567890</session></wpsoap:tagRspLogin></SOAP-ENV:Body></SOAP-ENV:Envelope> 這東西非常的復(fù)雜,我僅僅記錄一下使用到的部分。 1.2 關(guān)于gSOAP 引用:http://blog.csdn.net/darkone/archive/2006/12/14/1442525.aspx gSOAP編譯工具提供了一個(gè)SOAP/XML 關(guān)于C/C++ 語(yǔ)言的實(shí)現(xiàn), 從而讓C/C++語(yǔ)言開發(fā)web服務(wù)或客戶端程序的工作變得輕松 了很多。絕大多數(shù)的C++web服務(wù)工具包提供一組API函數(shù)類庫(kù) 來處理特定的SOAP數(shù)據(jù)結(jié)構(gòu),這樣就使得用戶必須改變程序 結(jié)構(gòu)來適應(yīng)相關(guān)的類庫(kù)。與之相反,gSOAP利用編譯器技術(shù)提 供了一組透明化的SOAP API,并將與開發(fā)無關(guān)的SOAP實(shí)現(xiàn)細(xì)節(jié) 相關(guān)的內(nèi)容對(duì)用戶隱藏起來。gSOAP的編譯器能夠自動(dòng)的將用 戶定義的本地化的C或C++數(shù)據(jù)類型轉(zhuǎn)變?yōu)榉蟈ML語(yǔ)法的數(shù)據(jù) 結(jié)構(gòu),反之亦然。這樣,只用一組簡(jiǎn)單的API就將用戶從SOAP 細(xì)節(jié)實(shí)現(xiàn)工作中解脫了出來,可以專注與應(yīng)用程序邏輯的實(shí) 現(xiàn)工作了。gSOAP編譯器可以集成C/C++和Fortran代碼(通過 一個(gè)Fortran到C的接口),嵌入式系統(tǒng),其他SOAP程序提供 的實(shí)時(shí)軟件的資源和信息;可以跨越多個(gè)操作系統(tǒng),語(yǔ)言環(huán) 境以及在防火墻后的不同組織。 gSOAP使編寫web服務(wù)的工作最小化了。gSOAP編譯器生成 SOAP的代碼來序列化或反序列化C/C++的數(shù)據(jù)結(jié)構(gòu)。gSOAP包 含一個(gè)WSDL生成器,用它來為你的web服務(wù)生成web服務(wù)的解 釋。gSOAP的解釋器及導(dǎo)入器可以使用戶不需要分析web服務(wù) 的細(xì)節(jié)就可以實(shí)現(xiàn)一個(gè)客戶端或服務(wù)端程序。 照我理解,gSOAP可以為我們生成soap服務(wù)器端+客戶端代碼的框架,我們只需實(shí)現(xiàn)具體的接口函數(shù)即可。而生成代碼的工具就是上面文中提到的“gSOAP編譯器”。 1.3 gSOAP編譯器(命令行工具) 1.3.1 wsdl2h 此工具用來從WSDL文件生成c/c++頭文件。 wsdl2h -o 頭文件名 WSDL文件名或URL 常用的其它參數(shù): -o 文件名,指定輸出頭文件 -n 名空間前綴 代替默認(rèn)的ns -c 產(chǎn)生純C代碼,否則是C++代碼 -s 不要使用STL代碼 -t 文件名,指定type map文件,默認(rèn)為typemap.dat -e 禁止為enum成員加上名空間前綴 1.3.2 socapcpp2 此工具用來從頭文件,生成SOAP服務(wù)器及客戶端代碼,還包括WSDL、測(cè)試用XML數(shù)據(jù)。 soapcpp2 頭文件 常用選項(xiàng) -C 僅生成客戶端代碼 -S 僅生成服務(wù)器端代碼 -L 不要產(chǎn)生soapClientLib.c和soapServerLib.c文件 -c 產(chǎn)生純C代碼,否則是C++代碼(與頭文件有關(guān)) -I 指定import路徑(見上文) -x 不要產(chǎn)生XML示例文件 -i 生成C++包裝,客戶端為xxxxProxy.h(.cpp),服務(wù)器端為xxxxService.h(.cpp)。 二、gSOAP開發(fā):Web Service服務(wù)端 開發(fā)服務(wù)器程序,需使用gSOAP生成服務(wù)器端代碼框架。我們有兩種做法:
這兩種方式,結(jié)果是一樣的,最終都有產(chǎn)生頭文件,并生成代碼。不同在于,在項(xiàng)目的開發(fā)中需要維護(hù)的文件不同,前者是需要維護(hù)WSDL文件,后者維護(hù)頭文件。 我個(gè)人覺得第二種方式更好用,不僅僅是少了個(gè)步驟,而是WSDL的語(yǔ)法太難寫了,有點(diǎn)XSD的味道。而頭文件的編寫,更接近于程序員的思考方式,比如定義消息結(jié)構(gòu),定義接口名稱等。 gSOAP是非常智能的,它利用C/C++的注釋來獲取信息,所以在手工編寫的頭文件中,注釋是用用處的,常以// gsoap 名字空間 …開頭。做為學(xué)習(xí),我準(zhǔn)備為php blog程序wordpress寫一個(gè)web service接口,名字叫wpsoap。 我開始寫頭文件(wpSoap.h)了,出于學(xué)習(xí)目的,我僅實(shí)現(xiàn)了兩個(gè)接口:一是用戶登陸;一是日志發(fā)布。 /** * @file wpsoap.h * @brief 為wordpress2.7提供web service接口 * * "http://gsoap"開頭行,請(qǐng)勿刪除. * * 1. 通過此文件生成WSDL 及 服務(wù)端代碼 * * >mkdir -p srvSrcFromH * >cd srvSrcFromH * >soapcpp2 -L -S "wpsoap.h" -I /path/to/gsoap-2.8/gsoap/import/ * * 2. 通過WSDL生成客戶端代碼 * * >mkdir -p clientSrcFromWSDL * >cd clientSrcFromWSDL * >wsdl2h.exe -o wpsoap.h ../srvSrcFromH/wpsoap.wsdl -I /path/to/gsoap-2.8/gsoap/import/ * >soapcpp2 -L -C wpsoap.h -I /path/to/gsoap-2.8/gsoap/import/ * * @author pansunyou@gmail.com * @version 1.0 * @date 2010-12-27 */ //gsoap wpsoap service name: wpsoap //gsoap wpsoap service namespace: http://www./wpsoap/ //gsoap wpsoap service location: http://192.168.0.187:10240/wpsoap/ //gsoap wpsoap service encoding: encoded //gsoap wpsoap schema namespace: urn:nszfpt #import "stlvector.h" //通用回復(fù) class wpsoap__tagCommResponse { int retCode ; //回復(fù)碼 std::string retMessage ; //回復(fù)消息 }; //[請(qǐng)求]用戶登陸 class wpsoap__tagReqLogin { std::string username ; //用戶名 std::string password ; //密碼名文 }; //[答復(fù)]用戶登陸 class wpsoap__tagRspLogin { wpsoap__tagCommResponse rsp ; //通用回復(fù) std::string session ; //會(huì)話標(biāo)識(shí) }; //[接口]登陸接口 int wpsoap__login(wpsoap__tagReqLogin req, wpsoap__tagRspLogin& rsp); //[請(qǐng)求]發(fā)布日志 class wpsoap__tagReqPost { std::string title ; //標(biāo)題 std::string body ; //正文 }; //[答復(fù)]發(fā)布日志 class wpsoap__tagRspPost { wpsoap__tagCommResponse rsp ; //通用回復(fù) }; //[接口]發(fā)布日志接口 int wpsoap__post(wpsoap__tagReqPost req, wpsoap__tagRspPost& rsp); 在接口中,我使用到了自定義的消息結(jié)構(gòu)wp_soap_tag*,這里的wpsoap__前綴是必須的,這樣soapcpp2才能為我們生成正確的代碼。 之后,我使用soapcpp2生成服務(wù)端代碼框架: @echo off @set path=%cd%\..\..\contrib\gsoap-2.8\gsoap\bin\win32\;%path% mkdir srvSrcFromH 2>nul cd srvSrcFromH soapcpp2.exe -L -S ..\res\wpSoap.h -I ..\..\..\contrib\gsoap-2.8\gsoap\importpause 要編譯出服務(wù)程序,有這些代碼還不夠,還需要自己寫兩個(gè)文件,一個(gè)用來寫main函數(shù),一個(gè)用來寫wpsoap的接口函數(shù)(當(dāng)然可以放在一個(gè)文件里)。最終我的服務(wù)器程序有以下文件:(另外,還需要gsoap目錄下的stdsoap2.cpp,因?yàn)槲野阉幾g為靜態(tài)庫(kù)了,所以這里沒列出來。) D:\wpSoapServer | makeSrc.bat | wpsoapimpl.cpp //這里實(shí)現(xiàn)了soapStub.h給出的接口 | wpsoapsrv.cpp //這里是main函數(shù)開始的地方 +---res | wpSoap.h \---srvSrcFromH soapC.cpp soapH.h soapServer.cpp soapStub.h soapwpsoapObject.h wpsoap.login.req.xml wpsoap.login.res.xml wpsoap.nsmap wpsoap.post.req.xml wpsoap.post.res.xml wpsoap.wsdl wpsoap.xsd 每次我修改了res/wpSoap.h后,我就運(yùn)行一下makeSrc.bat,自動(dòng)重新生成srvSrcFromH目錄里的所有東西,并且這個(gè)目錄里的所有代碼是不需要手工維護(hù)的(除非有特殊需要)。 在服務(wù)器代碼中,我僅實(shí)現(xiàn)了以下兩個(gè)函數(shù)(wpsoapimpl.cpp): int wpsoap__login(struct soap*, wpsoap__tagReqLogin req, wpsoap__tagRspLogin &rsp); int wpsoap__post(struct soap*, wpsoap__tagReqPost req, wpsoap__tagRspPost &rsp); wpsoapsrv.cpp里的代碼僅僅是調(diào)用gSOAP產(chǎn)生的代碼來建立socket服務(wù)器,基本不需維護(hù)。gSOAP是線程安全的,可以將請(qǐng)求分配到線程池內(nèi)實(shí)現(xiàn)高效服務(wù),但我僅為了走通gSOAP的使用流程,沒有這樣使用。 具體做法可以參考:http://www.cs./~engelen/soapdoc2.html 三、gSOAP開發(fā):Web Service客戶端 客戶端代碼本來也是可以通過為服務(wù)端編寫的頭文件生成的,但是為了真實(shí)一點(diǎn),假設(shè)我無法獲取服務(wù)器開發(fā)時(shí)使用的頭文件,僅僅有個(gè)公開的WSDL文件,就是上面產(chǎn)生的srvSrcFromH /wpsoap.wsdl。 我用這個(gè)腳本來生成客戶端框架代碼: @echo off @set path=%cd%\..\..\contrib\gsoap-2.8\gsoap\bin\win32\;%path% mkdir clientSrcFromWSDL 2>nul cd clientSrcFromWSDL wsdl2h.exe -o wpsoap.h ..\..\wpSoapServer\srvSrcFromH\wpsoap.wsdl soapcpp2.exe -L -C wpsoap.h -I ..\..\..\contrib\gsoap-2.8\gsoap\importpause 加上我測(cè)試用的代碼wpsoapclient.cpp,以及gosap目錄里的stdsoap2.cpp,我有了如下文件: D:\wpSoapClient | makeSrc.bat | wpsoapclient.cpp \---clientSrcFromWSDL soapC.cpp soapClient.cpp soapH.h soapStub.h soapwpsoapProxy.h wpsoap.h wpsoap.login.req.xml wpsoap.login.res.xml wpsoap.nsmap wpsoap.post.req.xml wpsoap.post.res.xml 客戶端代碼非常少(僅僅是實(shí)現(xiàn),容錯(cuò)之類的都未考慮): /** * @file wpsoapclient.cpp * @brief 訪問wpsoap服務(wù) * * 調(diào)用wpsoap的客戶端示例代碼 * * @author pansunyou@gmail.com * @version 1.0 * @date 2010-12-27 */ #define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <cstdlib> #include <string> #include "clientSrcFromWSDL/soapStub.h" #include "clientSrcFromWSDL/soapwpsoapProxy.h" #include "clientSrcFromWSDL/wpsoap.nsmap" using namespace std; int main(int argc, char*argv[]) { wpsoap wpsoapClient; if (argc==2) wpsoapClient.endpoint = argv[1]; //1. 登陸 string username = "admin"; string password = "3.14159"; int r = 0; ns2__tagReqLogin req; req.username = username; req.password = password; _ns2__login ns2__login; ns2__login.req = &req; _ns2__tagRspLogin rsp; r = wpsoapClient.__ns1__login(&ns2__login, &rsp); if (r!=0) { fprintf(stderr, "調(diào)用soap接口失敗!\n"); return -1; } if (0!=rsp.rsp->retCode) { printf("登陸失敗 retCode=%d, retMessage=%s\n", rsp.rsp->retCode, rsp.rsp->retMessage.c_str()); return -1; } printf("登陸成功! [session=%s]\n", rsp.session.c_str()); ns2__tagReqPost reqPost; reqPost.body = "post article by wpsoap!"; reqPost.title = "hello, wpsoap!"; _ns2__post ns2__post; ns2__post.req = &reqPost; _ns2__tagRspPost ns2__tagRspPost; r = wpsoapClient.__ns1__post(&ns2__post, &ns2__tagRspPost); if (r!=0) { fprintf(stderr, "調(diào)用soap接口失敗!\n"); return -1; } if (0!=rsp.rsp->retCode) { printf("發(fā)布日志失敗 retCode=%d, retMessage=%s\n", rsp.rsp->retCode, rsp.rsp->retMessage.c_str()); return -1; } printf("日志發(fā)布成功! [retMessage=%s]\n", rsp.rsp->retMessage.c_str()); return 0; } |
|