目錄一、基本概念 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),即簡單對象訪問協(xié)議,是在分布式的環(huán)境中交換數(shù)據(jù)的簡單協(xié)議,以XML作為數(shù)據(jù)傳送語言。
SOAP有兩種工作模式,一種是RPC(Remote Procedure Call),另一種是Message-Oriented。MO可以利用XML來交換結(jié)構(gòu)更復(fù)雜的數(shù)據(jù)。RPC模式的SOAP可以理解為這樣一個開發(fā)協(xié)議:SOAP=RPC+HTTP+XML,具有以下特點:
看一個簡單的請求及回復(fù)SOAP數(shù)據(jù)(真實數(shù)據(jù)):
POST /wpsoap/ HTTP/1.1Host: 127.0.0.1:10240User-Agent: gSOAP/2.7Content-Type: text/xml; charset=utf-8; action=""Content-Length: 480Connection: closeSOAPAction: "" <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://www.example.org/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 OKServer: gSOAP/2.7Content-Type: text/xml; charset=utf-8; action=""Content-Length: 555Connection: close <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wpsoap="urn:nszfpt"><SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/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.aspxgSOAP編譯工具提供了一個SOAP/XML 關(guān)于C/C++ 語言的實現(xiàn),從而讓C/C++語言開發(fā)web服務(wù)或客戶端程序的工作變得輕松了很多。絕大多數(shù)的C++web服務(wù)工具包提供一組API函數(shù)類庫來處理特定的SOAP數(shù)據(jù)結(jié)構(gòu),這樣就使得用戶必須改變程序結(jié)構(gòu)來適應(yīng)相關(guān)的類庫。與之相反,gSOAP利用編譯器技術(shù)提供了一組透明化的SOAP API,并將與開發(fā)無關(guān)的SOAP實現(xiàn)細節(jié)相關(guān)的內(nèi)容對用戶隱藏起來。gSOAP的編譯器能夠自動的將用戶定義的本地化的C或C++數(shù)據(jù)類型轉(zhuǎn)變?yōu)榉蟈ML語法的數(shù)據(jù)結(jié)構(gòu),反之亦然。這樣,只用一組簡單的API就將用戶從SOAP細節(jié)實現(xiàn)工作中解脫了出來,可以專注與應(yīng)用程序邏輯的實現(xiàn)工作了。gSOAP編譯器可以集成C/C++和Fortran代碼(通過一個Fortran到C的接口),嵌入式系統(tǒng),其他SOAP程序提供的實時軟件的資源和信息;可以跨越多個操作系統(tǒng),語言環(huán)境以及在防火墻后的不同組織。 gSOAP使編寫web服務(wù)的工作最小化了。gSOAP編譯器生成SOAP的代碼來序列化或反序列化C/C++的數(shù)據(jù)結(jié)構(gòu)。gSOAP包含一個WSDL生成器,用它來為你的web服務(wù)生成web服務(wù)的解釋。gSOAP的解釋器及導(dǎo)入器可以使用戶不需要分析web服務(wù)的細節(jié)就可以實現(xiàn)一個客戶端或服務(wù)端程序。
照我理解,gSOAP可以為我們生成soap服務(wù)器端+客戶端代碼的框架,我們只需實現(xiàn)具體的接口函數(shù)即可。而生成代碼的工具就是上面文中提到的“gSOAP編譯器”。
1.3 gSOAP編譯器(命令行工具)
1.3.1 wsdl2h
此工具用來從WSDL文件生成c/c++頭文件。
wsdl2h -o 頭文件名 WSDL文件名或URL常用的其它參數(shù):-o 文件名,指定輸出頭文件-n 名空間前綴 代替默認的ns-c 產(chǎn)生純C代碼,否則是C++代碼-s 不要使用STL代碼-t 文件名,指定type map文件,默認為typemap.dat-e 禁止為enum成員加上名空間前綴
1.3.2 socapcpp2
此工具用來從頭文件,生成SOAP服務(wù)器及客戶端代碼,還包括WSDL、測試用XML數(shù)據(jù)。
soapcpp2 頭文件常用選項-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)生頭文件,并生成代碼。不同在于,在項目的開發(fā)中需要維護的文件不同,前者是需要維護WSDL文件,后者維護頭文件。
我個人覺得第二種方式更好用,不僅僅是少了個步驟,而是WSDL的語法太難寫了,有點XSD的味道。而頭文件的編寫,更接近于程序員的思考方式,比如定義消息結(jié)構(gòu),定義接口名稱等。
gSOAP是非常智能的,它利用C/C++的注釋來獲取信息,所以在手工編寫的頭文件中,注釋是用用處的,常以// gsoap 名字空間 …開頭。做為學(xué)習(xí),我準備為php blog程序wordpress寫一個web service接口,名字叫wpsoap。
我開始寫頭文件(wpSoap.h)了,出于學(xué)習(xí)目的,我僅實現(xiàn)了兩個接口:一是用戶登陸;一是日志發(fā)布。
/** * @file wpsoap.h * @brief 為wordpress2.7提供web service接口 * * "http://gsoap"開頭行,請勿刪除. * * 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.example.org/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ù)消息}; //[請求]用戶登陸class wpsoap__tagReqLogin{ std::string username ; //用戶名 std::string password ; //密碼名文}; //[答復(fù)]用戶登陸class wpsoap__tagRspLogin{ wpsoap__tagCommResponse rsp ; //通用回復(fù) std::string session ; //會話標識}; //[接口]登陸接口int wpsoap__login(wpsoap__tagReqLogin req, wpsoap__tagRspLogin& rsp); //[請求]發(fā)布日志class wpsoap__tagReqPost{ std::string title ; //標題 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>nulcd srvSrcFromHsoapcpp2.exe -L -S ..\res\wpSoap.h -I ..\..\..\contrib\gsoap-2.8\gsoap\importpause
要編譯出服務(wù)程序,有這些代碼還不夠,還需要自己寫兩個文件,一個用來寫main函數(shù),一個用來寫wpsoap的接口函數(shù)(當然可以放在一個文件里)。最終我的服務(wù)器程序有以下文件:(另外,還需要gsoap目錄下的stdsoap2.cpp,因為我把它編譯為靜態(tài)庫了,所以這里沒列出來。)
D:\wpSoapServer| makeSrc.bat| wpsoapimpl.cpp //這里實現(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后,我就運行一下makeSrc.bat,自動重新生成srvSrcFromH目錄里的所有東西,并且這個目錄里的所有代碼是不需要手工維護的(除非有特殊需要)。
在服務(wù)器代碼中,我僅實現(xiàn)了以下兩個函數(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ù)器,基本不需維護。gSOAP是線程安全的,可以將請求分配到線程池內(nèi)實現(xiàn)高效服務(wù),但我僅為了走通gSOAP的使用流程,沒有這樣使用。
具體做法可以參考:http://www.cs.fsu.edu/~engelen/soapdoc2.html
三、gSOAP開發(fā):Web Service客戶端
客戶端代碼本來也是可以通過為服務(wù)端編寫的頭文件生成的,但是為了真實一點,假設(shè)我無法獲取服務(wù)器開發(fā)時使用的頭文件,僅僅有個公開的WSDL文件,就是上面產(chǎn)生的srvSrcFromH /wpsoap.wsdl。
我用這個腳本來生成客戶端框架代碼:
@echo off@set path=%cd%\..\..\contrib\gsoap-2.8\gsoap\bin\win32\;%path% mkdir clientSrcFromWSDL 2>nulcd clientSrcFromWSDLwsdl2h.exe -o wpsoap.h ..\..\wpSoapServer\srvSrcFromH\wpsoap.wsdlsoapcpp2.exe -L -C wpsoap.h -I ..\..\..\contrib\gsoap-2.8\gsoap\importpause
加上我測試用的代碼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
客戶端代碼非常少(僅僅是實現(xiàn),容錯之類的都未考慮):
/** * @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;}