gSOAP編譯工具提供了一個SOAP/XML 關於C/C++ 語言的實現,從而讓C/C++語言開發web服務或用戶端程式的工作變得輕鬆了很多。絕大多數的C++web服務工具包提供一組API函數類庫來處理特定的SOAP資料結構,這樣就使得使用者必須改變程式結構來適應相關的類庫。與之相反,gSOAP利用編譯器技術提供了一組透明化的SOAP API,並將與開發無關的SOAP實現細節相關的內容對使用者隱藏起來。
gSOAP的編譯器能夠自動的將使用者定義的本地化的C或C++資料類型轉變為符合XML文法的資料結構,反之亦然。這樣,只用一組簡單的API就將使用者從SOAP細節實現工作中解脫了出來,可以專註與應用程式邏輯的實現工作了。gSOAP編譯器可以整合C/C++和Fortran代碼(通過一個Fortran到C的介面),嵌入式系統,其他SOAP程式提供的即時軟體的資源和資訊;可以跨越多個作業系統,語言環境以及在防火牆後的不同組織。
gSOAP使編寫web服務的工作最小化了。gSOAP編譯器產生SOAP的代碼來序列化或還原序列化C/C++的資料結構。gSOAP包含一個WSDL產生器,用它
來為你的web服務產生web服務的解釋。gSOAP的解譯器及匯入器可以使使用者不需要分析web服務的細節就可以實現一個用戶端或服務端程式。
下面是gSOAP的一些特點:
gSOAP編譯器可以根據使用者定義的C和C++資料結構自動產生符合SOAP的執行個體化代碼。
gSOAP支援WSDL 1.1, SOAP 1.1, SOAP 1.2, SOAP RPC 編碼方式以及 literal/document 方式. gSOAP是少數完全支援SOAP1.1 RPC編碼功能的工具包,包括多維陣列及動態類型。比如,一個包含一個基類參數的遠程方法可以接收用戶端 傳來的子類執行個體。子類執行個體通過動態綁定技術來保持一致性。 gSOAP 支援 MIME (SwA) 和 DIME 附件包。 gSOAP是唯一支援DIME附件傳輸的工具包。它允許你在保證XML可用性的同時能夠以最快的方式(流方式)傳遞近乎無大小限制的位元據 。 gSOAP 支援 SOAP-over-UDP。 gSOAP 支援 IPv4 and IPv6. gSOAP 支援 Zlib deflate and gzip compression(for HTTP, TCP/IP, and XML file storage)。 gSOAP 支援 SSL (HTTPS)。 gSOAP 支援 HTTP/1.0, HTTP/1.1 保持串連, 分塊傳輸及基本驗證。 gSOAP 支援 SOAP 單向訊息。 gSOAP 包含一個 WSDL 產生器,便於web服務的發布。 gSOAP 包含一個WSDL解析器(將WSDL轉換為gSOAP標頭檔),可以自動化使用者用戶端及服務端的開發。 產生可以單獨啟動並執行web服務及用戶端程式。 因為只需要很少記憶體空間,所以可以運行在類似Palm OS, Symbian, Pocket PC的小型裝置中。 適用於以C或C++開發的web服務中。 跨平台:Windows, Unix, Linux, Mac OS X, Pocket PC, Palm OS, Symbian等。 支援序列化程式中的本地化C/C++資料結構。 可以使用輸入和輸出緩衝區來提高效率,但是不用完全訊息緩衝來確定HTTP訊息的長度。取而代之的是一個三相序列化方法。這樣,像64位 編碼的映像就可以在小記憶體裝置(如PDA)中以DIME附件或其他方式傳輸。 支援C++單繼承,動態綁定,重載,指標結構(列表、樹、圖、循環圖表,定長數組,動態數組,枚舉,64位2進位編碼及16進位編碼)。 不需要重寫現有的C/C++應用。但是,不能用unions,指標和null 指標來作為遠程方法調用參數的資料結構中元素。 三相編組:1)分析指標,引用,迴圈資料結構;2)確定HTTP訊息長度;3)將資料序列化位SOAP1.1編碼方式或使用者定義的資料編碼方式。 雙相編組:1)SOAP解釋及編碼;2)分解“forward”指標(例如:分解SOAP中的href屬性)。 完整可定製的SOAP錯誤處理機制。 可定製的SOAP訊息頭處理機制,可以用來保持狀態資訊 2 gSoap2.2版與gSOAP 2.1版(或以前版本)的不同 如果你是從2.1版升級到2.2或以後版本,請注意這些變化。 為了能夠分離傳輸、內容編碼、映射中的接收/發送設定,改變了運行時選項及標誌。這些標誌分布再四個類中:傳輸(IO),內容編碼(ENC ),XML編組(XML)及C/C++資料對應。不再提倡使用舊標誌soap_disable_X及soap_enable_X(其中,X表示選項名)。具體內容請參見9.12節 。 3. gSoap2.x版與gSOAP 1.x版的不同 如果你是從1.x版升級到2.x版,請注意下面的內容。 gSOAP2.0及之後的版本是在1.x版基礎上重寫的。gSOAP2.0之後的版本是安全執行緒的,但之前版本不是。gSOAP2.x版本中的主要檔案已經重新 命名,以便與1.x版區分。 gSOAP 1.X gSOAP 2.X soapcpp soapcpp2 soapcpp.exe soapcpp2.exe stdsoap.h stdsoap2.h stdsoap.c stdsoap2.c stdsoap.cpp stdsoap2.cpp 從1.x版升級到2.x版並不需要進行大量的代碼重寫工作。所有2.x版相關的函數都定義在stdsoap2.c[pp]檔案中,這個檔案是由gSOAP編譯器自 動產生的。所以,用1.x版開發的服務端或用戶端代碼需要進行修改以適應2.x版中函數的變化:在2.x版中,所有的gSOAP函數都增加了一個參 數用來儲存一個gSOAP運行環境執行個體。這個參數包括了檔案描述,表,緩衝,標誌位等,它在所有gSOAP函數中都是第一個參數。 gSOAP運行環境執行個體是一個struct soap類型的變數。當用戶端程式訪問遠程方法前或當服務端程式能夠接收一個請求前,必須先將這個運行環 境變數初始化。在2.x版中新增了3個函數來負責這些事情: 函數 解釋 soap_init(struct soap *soap) 初始化環境變數(只需執行一次) struct soap *soap_new() 定義並初始化環境變數並返回一個該變數的指標 struct soap *soap_copy(struct soap *soap) 定義一個環境變數並從已有的環境變數中拷貝環境資訊 環境變數定義好後就可以重複使用而不必再次初始化了。只有當線程獨佔訪問時,我們才需要一個新的環境變數。例如,下面的代碼分配了 一個用於多個遠程方法的環境變數: int main() { struct soap soap; ... soap_init(&soap); // 初始化環境變數 ... soap_call_ns__method1(&soap, ...); // 調用一個遠程方法 ... soap_call_ns__method2(&soap, ...); // 調用另一個遠程方法 ... soap_end(&soap); // 清除環境變數 ... } 我們也可以像下面這樣定義環境變數: int main() { struct soap *soap; ... soap = soap_new(); // 定義並初始化環境變數 if (!soap) // 如果不能定義,退出 ... soap_call_ns__method1(soap, ...); // 調用遠程函數 ... soap_call_ns__method2(soap, ...); // 調用另一個遠程函數 ... soap_end(soap); // 清除環境變數 ... free(soap); // 釋放環境變數空間 } 服務端代碼在調用soap_serve函數前,需要定義相關環境變數: int main() { struct soap soap; soap_init(&soap); soap_serve(&soap); } 或者像下面這樣: int main() { soap_serve(soap_new()); } soap_serve函數用來處理一個或多個(當允許HTTP keep-alive時,參見18.11節中的SOAP_IO_KEEPALIVE標誌)請求。 一個web服務可以用多線程技術來處理請求: int main() { struct soap soap1, soap2; pthread_t tid; ... soap_init(&soap1); if (soap_bind(&soap1, host, port, backlog) < 0) exit(1); if (soap_accept(&soap1) < 0) exit(1); pthread_create(&tid, NULL, (void*(*)(void*))soap_serve, (void*)&soap1); ... soap_init(&soap2); soap_call_ns__method(&soap2, ...); // 調用遠程方法 ... soap_end(&soap2); ... pthread_join(tid, NULL); // 等待線程結束 soap_end(&soap1); // 釋放環境變數 } 在上面的例子中,需要兩個環境變數資訊。而在1.x版本中,由於靜態分配環境變數,多線程技術是不被允許的(只有一個線程可以用這個環 境變數調用遠程方法或處理請求資訊)。 4 準備工作 要開始用gSOAP建立一個web服務應用, 你需要: 一個C/C++編譯器. 擁有根據作業系統平台建立的可執行檔gSOAP的stdsoap2(windows下為stdsoap2.exe)編譯器。 擁有根據作業系統平台建立的可執行檔gSOAP的wsdl2h(windows下為wsdl2h.exe)WSDL解析器。 需要'stdsoap2.c'或'stdsoap2.cpp'及'stdsoap2.h'檔案來實現你的SOAP功能。你可以建立一個dll或動態庫以便簡化串連。 如果你要支援SSL(HTTPS)及壓縮的話,可以安裝OpenSSL及Zlib庫。 gSOAP是獨立開發包,不需要任何第三方的軟體支援(除非你要用到OpenSSL及Zlib)。 與平台無關的gSOAP版本需要你下面的工具編譯'soapcpp2'及'wsdl2h'檔案: 一個C++編譯器(用來編譯'wsdl2h'WSDL解析器)。 Bison 或 Yacc Flex 或 Lex 推薦使用Bison及Flex。 在軟體包samples目錄下有大量的開發執行個體。可以用'make'來編譯這些例子。這些例子包含了gSOAP中的各個方面。其中,最簡單的例子是 one-liners(samples/oneliners)。 5 快速指南 本指南旨在讓你快速開始你的gSOAP開發之旅。閱讀本節的內容,需要你對SOAP 1.1協議及C/C++文法有大體的瞭解。雖然使用gSOAP編譯器可 以直接用C/C++開始編寫web服務及用戶端程式而不需要瞭解SOAP協議的細節,但是由於我們在本節中使用了大量的執行個體來說明gSOAP與其他SOAP 實現的串連及通訊,所以瞭解一些SOAP及WSDL協議也是必需的。 5.1 如何使用gSOAP編譯環境來編譯SOAP用戶端程式 通常,一個SOAP用戶端應用的實現需要為每個用戶端需要調用的遠程方法提供一個存根常式(stub routine)。存根常式主要負責編碼參數信 息;將包含參數資訊的調用請求發送給制定的SOAP服務;等待返回結果;將結果中的參數資訊編碼。用戶端程式調用訪問遠程方法的存根常式 就像調用本地方法一樣。用C/C++手工別寫一個存根常式是個十分痛苦的差使,尤其當遠程方法的參數中包含特定的資料結構(如:記錄、數組 、圖等)時。幸運的是,gSOAP包中的'wsdl2h'WSDL解析器和'soapcpp2’存根及架構編譯器能夠將web服務用戶端及服務端的開發工作自動化。 'soapcpp2’存根及架構編譯器是可以產生構建C++ SOAP用戶端所需的C++源碼的先行編譯器。該先行編譯器的輸入參數是一個標準的C/C++標頭檔 。這個標頭檔可以由WSDL解析器根據相關的WSDL文檔自動產生。 參見下面的命令: $ wsdl2h -o quote.h 上面的命令根據制定URL提供的WSDL文檔產生一個C++文法結構的標頭檔。 如果需要產生一個純C的標頭檔,需要一下命令: $ wsdl2h -c -o quote.h 更多關於WSDL解析器及其選項的細節資訊,請參見8.2.10節。 執行上述命令後,quote.h檔案就產生了。其中包含開發用戶端或服務端程式的存根常式定義。SOAP服務遠程方法以函式宣告的方式在這個頭 檔案中被定義。C/C++原始碼的存根常式將通過先行編譯器自動實現。同時,每個遠程方法的程式架構也被自動產生了,它可以用來建立SOAP服務 端程式應用。 SOAP服務的輸入輸出參數可以是簡單的資料類型或複雜的資料結構,可以由WSDL解析器自動產生或手工定義。先行編譯器將自動產生序列化/反 序列化這些資料的代碼,以便存根常式可以將這些資料以XML的方式編碼或解碼。 官方網站http://gsoap2.sourceforge.net/ 下面給出一個例子吧wsdl2h -o outfile.h(為自己任意起的標頭檔) infile.wsdl(提供的wsdl檔案)
如果用產生純C的代碼,需要加編譯選項-c
wsdl2h -c -o outfile.h(為自己任意起的標頭檔) infile.wsdl(提供的wsdl檔案)根據產生的outfile.h檔案,用工具soapcpp2 outfile.h產生構架代碼
如果用產生純C的代碼,需要加編譯選項-c
soapcpp2 -c outfile.h產生構架代碼注意:在編譯的過程中會提示找不到檔案stlvector.h,你需要在gsoap\import下把這個檔案拷貝過來。作為服務端,需要的代碼為:
soapC.cpp soapC.h soapServer.cpp soapStub.h stdsoap2.cpp stdsoap2.h stlvector.h WcmpServiceSOAP11Binding.nsmap(這個檔案名稱根據outfile檔案是不同的)
作為用戶端,需要的代碼為:
soapC.cpp soapC.h soapClient.cpp soapStub.h stdsoap2.cpp stdsoap2.h stlvector.h WcmpServiceSOAP11Binding.nsmap(這個檔案名稱根據outfile檔案是不同的)將檔案匯入工程編譯的時候需要設定還需要WSSOCK.LIB,因為底層是socket實現的主函數內容如下:
#include "stdafx.h"#include "soapH.h"#include <stdio.h>#include "calc.nsmap"using namespace std;int main(int argc, char **argv){ SOAP_SOCKET m, s; /* master and slave sockets */ struct soap soap; soap_init(&soap); if (argc < 2) soap_serve(&soap);/* serve as CGI application */ else { m = soap_bind(&soap, NULL, atoi(argv[1]), 100); if (!soap_valid_socket(m)) { soap_print_fault(&soap, stderr); exit(-1); } fprintf(stderr, "Socket connection successful: master socket = %d\n", m); for ( ; ; ) { s = soap_accept(&soap); fprintf(stderr, "Socket connection successful: slave socket = %d\n", s); if (!soap_valid_socket(s)) { soap_print_fault(&soap, stderr); exit(-1); } soap_serve(&soap); soap_end(&soap); } } return 0;} int __cdecl ns2__add(struct soap *soap,double a,double b,double &result){result = a + b;cout<<"the result is ---"<<result<<endl;return SOAP_OK;}int __cdecl ns2__sub(struct soap *soap, double a, double b, double &result){result = a - b;return SOAP_OK;}int __cdecl ns2__mul(struct soap *soap, double a, double b, double &result){result = a * b;return SOAP_OK;}int __cdecl ns2__div(struct soap *soap, double a, double b, double &result){if (b) result = a / b; else { char *s = (char*)soap_malloc(soap, 1024); sprintf(s, "<error xmlns=\"http://tempuri.org/\">Can't divide %f by %f</error>", a, b); return soap_sender_fault(soap, "Division by zero", s); } return SOAP_OK;}int __cdecl ns2__pow(struct soap *soap, double a, double b, double &result){result = pow(a, b); if (soap_errno == EDOM)/* soap_errno is like errno, but compatible with Win32 */ { char *s = (char*)soap_malloc(soap, 1024); sprintf(s, "Can't take the power of %f to %f", a, b); sprintf(s, "<error xmlns=\"http://tempuri.org/\">Can't take power of %f to %f</error>", a, b); return soap_sender_fault(soap, "Power function domain error", s); } return SOAP_OK;}已同樣的方式匯入用戶端程式
#include "stdafx.h"#include "soapH.h"#include "calc.nsmap"//const char server[] = "http://websrv.cs.fsu.edu/~engelen/calcserver.cgi";const char server[] = "http://localhost:8000";int main(int argc, char* argv[]){struct soap soap;double a, b, result; if (argc < 4) { fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num\n");exit(0); }soap_init1(&soap, SOAP_XML_INDENT); a = strtod(argv[2], NULL); b = strtod(argv[3], NULL); switch (*argv[1]) { case 'a': soap_call_ns2__add(&soap, server, "", a, b, result); break; case 's': soap_call_ns2__sub(&soap, server, "", a, b, result); break; case 'm': soap_call_ns2__mul(&soap, server, "", a, b, result); break; case 'd': soap_call_ns2__div(&soap, server, "", a, b, result); break; case 'p': soap_call_ns2__pow(&soap, server, "", a, b, result); break; default: fprintf(stderr, "Unknown command\n"); exit(0); } if (soap.error) { soap_print_fault(&soap, stderr); exit(1); } else printf("result = %g\n", result); soap_destroy(&soap); soap_end(&soap); soap_done(&soap);return 0;}啟動gsoap服務調用用戶端程式,執行服務同樣的程式可以部署到linux或者unix中。