C++中gSOAP的使用詳解
本文主要介紹C++中gSOAP的使用方法,附帶介紹SOAP協議的基礎知識,適用於第一次使用gSOAP的開發人員。gSOAP官網上的示例代碼存在一些錯誤,對初次接觸的人不太友好,本文是在官方示例calc++的基礎上進行瞭一些補充、改動。
SOAP簡介
SOAP 是一種簡單的基於 XML 的協議,它使應用程序通過 HTTP 來交換信息,具體內容可以參考SOAP 教程。SOAP的本質是通過HTTP協議以XML格式進行數據交互,隻不過這個XML格式的定義是大傢公認的。
使用SOAP時需註意,SOAP的XML命名空間由於版本的不同可能存在差異(如soapevn
、SOAP-ENV
),在調用SOAP服務前最好確認服務器的XML格式。
gSOAP
gSOAP 有商業版、開源版兩個版本,開源版使用GPLv2開源協議,支持多個操作系統,具體內容參考github或者官網。
gSOAP提供瞭一組編譯工具(可以認為是代碼生成器)和一些庫文件,簡化C/C++語言開發web服務或客戶端程序的工作,開發人員可以專註於實現應用程序的邏輯:
- 編譯工具提供瞭一個SOAP/XML 關於C/C++ 語言的實現,能夠自動完成本地C或C++數據類型和XML數據結構之間的轉換。
- 庫文件提供瞭SOAP報文生成、HTTP協議通訊的實現,及相關的配套設施,用於最終的SOAP報文的生成、傳輸。
本文使用的庫文件主要是以下幾個:
stdsoap2.h
、stdsoap2.cpp
:HTTP協議的實現、最終的SOAP報文生成,如果是C語言則使用stdsoap2.h、stdsoap2.ctypemap.dat
: wsdl2h工具根據wsdl文件生成頭文件時需要此文件,可以更改項目的xml命名空間(後面再細說)threads.h
:實現高性能的多線程服務器需要的文件,可以並發處理請求,並且在服務操作變得耗時時不會阻塞其他客戶端請求
準備工作
先進入官網的下載頁面,然後選擇開源版本:
也可以直接點擊開源版本的官方下載鏈接或https://pan.baidu.com/s/156PEw9OMbzQel39Oozknow提取碼: gy4x。
將下載的壓縮包解壓(本文使用的是gsoap_2.8.117.zip),解壓後的文件放到自己習慣的位置(推薦放到C盤)。
在命令行提示符窗口中,使用cd命令進入..\gsoap_2.8.117\gsoap-2.8\gsoap\bin\win64目錄:
註:後面需要把頭文件、typemap.dat放到該程序目錄下,生成的文件也在這裡。
頭文件
使用gSOAP的編譯工具生成代碼,需要一個定義API的頭文件,此處使用官方示例中的calc.h頭文件,對兩個數字進行加、減、乘、除或乘方運算:。
calc.h
頭文件可以通過wsdl2h工具自動生成(需要Web 服務的 WSDL文件 ),運行的cmd命令如下:
wsdl2h -o calc.h http://www.genivia.com/calc.wsdl
也可以手動定義一個calc.h頭文件,內容如下:
//gsoap ns service method add Sums two values int ns__add(double a, double b, double &result); //gsoap ns service method sub Subtracts two values int ns__sub(double a, double b, double &result); //gsoap ns service method mul Multiplies two values int ns__mul(double a, double b, double &result); //gsoap ns service method div Divides two values int ns__div(double a, double b, double &result); //gsoap ns service method pow Raises a to b int ns__pow(double a, double b, double &result);
構建客戶端應用程序
客戶端應用程序在命令行中運行並使用命令行參數調用計算器 Web 服務來對兩個數字進行加、減、乘、除或乘方運算。
生成soap源碼
為客戶端生成服務和數據綁定接口:
soapcpp2 -j -r -CL calc.h
其中 option-j生成 C++ 代理類,option-r生成報告,option-CL僅生成客戶端,而沒有(未使用的)lib 文件。
這會生成以下幾個文件:
其中,xml文件是SOAP報文的示例,便於後期的調試,以add方法為例。
add方法的請求報文ns.add.req.xml內容如下:
<?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:ns="http://tempuri.org/ns.xsd"> <SOAP-ENV:Body> <ns:add> <a>0.0</a> <b>0.0</b> </ns:add> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
add方法的響應報文ns.add.req.xml內容如下:
<?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:ns="http://tempuri.org/ns.xsd"> <SOAP-ENV:Body> <ns:addResponse> <result>0.0</result> </ns:addResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
建立客戶端項目
建立一個C++的控制臺應用項目,引入上面生成的幾個文件:
soapStub.h
: 沒有註釋的純 C/C++ 頭文件語法的規范副本。soapH.h
:聲明 XML 序列化程序。soapC.cpp
: 實現 XML 序列化程序。soapProxy.h
: 定義客戶端 XML 服務 API 類Proxy。soapProxy.cpp
: 實現客戶端 XML 服務 API 類Proxy。calc.nsmap
: XML 命名空間綁定表到 #include。
還需要引入gSOAP解壓目錄下的stdsoap2.h和stdsoap2.cpp文件。
為方便調試,客戶端程序示例改為固定參數調用add方法,代碼如下:
#include "soapProxy.h" #include "ns.nsmap" /* the Web service endpoint URL */ const char server[] = "http://localhost:8080"; int main(int argc, char** argv) { /*if (argc < 4) { fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num\n"); exit(1); }*/ Proxy calc(server); double a, b, result; /*a = strtod(argv[2], NULL); b = strtod(argv[3], NULL);*/ a = 3; b = 23; /*switch (*argv[1])*/ switch ('a') { case 'a': calc.add(a, b, result); break; case 's': calc.sub(a, b, result); break; case 'm': calc.mul(a, b, result); break; case 'd': calc.div(a, b, result); break; case 'p': calc.pow(a, b, result); break; default: fprintf(stderr, "Unknown command\n"); exit(1); } if (calc.soap->error) calc.soap_stream_fault(std::cerr); else std::cout << "result = " << result << std::endl; calc.destroy(); /* clean up */ std::cout << std::endl; return 0; }
構建服務端應用程序
實現一個獨立的迭代服務器,它接受主機端口上的傳入請求,支持多線程,可以並發處理請求,並且在服務操作變得耗時時不會阻塞其他客戶端請求。
生成SOAP源碼
為服務器端生成服務和數據綁定接口:
soapcpp2 -j -r -SL calc.h
其中 option-j生成 C++ 服務類,option-r生成報告,option-SL僅生成服務器端,而沒有(未使用的)lib 文件。
生成的文件和客戶端的文件名稱一樣,隻是內容不同。
建立服務端項目
建立一個C++的控制臺應用項目,引入的生成文件和客戶端的相同。為瞭支持多線程,需要引入文件threads.h。
服務端代碼如下:
#include "soapService.h" #include "ns.nsmap" #include "threads.h" int port = 8080; void* process_request(void* arg) { Service* service = (Service*)arg; THREAD_DETACH(THREAD_ID); if (service) { service->serve(); service->destroy(); /* clean up */ delete service; } return NULL; } int main() { Service service(SOAP_IO_KEEPALIVE); /* enable HTTP kee-alive */ service.soap->send_timeout = service.soap->recv_timeout = 5; /* 5 sec socket idle timeout */ service.soap->transfer_timeout = 30; /* 30 sec message transfer timeout */ SOAP_SOCKET m = service.bind(NULL, port, 100); /* master socket */ if (soap_valid_socket(m)) { while (soap_valid_socket(service.accept())) { THREAD_TYPE tid; void* arg = (void*)service.copy(); /* use updated THREAD_CREATE from plugin/threads.h https://www.genivia.com/files/threads.zip */ if (arg) while (THREAD_CREATE(&tid, (void* (*)(void*))process_request, arg)) Sleep(1); } } service.soap_stream_fault(std::cerr); service.destroy(); /* clean up */ return 0; } /* service operation function */ int Service::add(double a, double b, double& result) { result = a + b; return SOAP_OK; } /* service operation function */ int Service::sub(double a, double b, double& result) { result = a - b; return SOAP_OK; } /* service operation function */ int Service::mul(double a, double b, double& result) { result = a * b; return SOAP_OK; } /* service operation function */ int Service::div(double a, double b, double& result) { if (b) result = a / b; else return soap_senderfault("Division by zero", NULL); return SOAP_OK; } /* service operation function */ int Service::pow(double a, double b, double& result) { result = ::pow(a, b); if (soap_errno == EDOM) /* soap_errno is like errno, but portable */ return soap_senderfault("Power function domain error", NULL); return SOAP_OK; }
打印報文
在實際調試中,需要確定SOAP協議過程中具體的報文,隻需要改動stdsoap2.cpp
源碼即可。參考gsoap報文打印,實現保存最後一次報文到特定的文件。
在stdsoap2.h
頭文件include下添加fstream
,內容如下:
#include "stdsoap2.h" #include <fstream>
soap_begin_recv函數開始處添加以下代碼:
//發送完請求報文 獲取請求報文信息(作為客戶端的時候) std::string str_reqXml = ""; std::string strBuf; std::string::size_type pos1 = std::string::npos; std::string::size_type pos2 = std::string::npos; strBuf = soap->buf; pos1 = strBuf.find("<?xml", 0); pos2 = strBuf.find("</SOAP-ENV:Envelope>", 0); if (pos1 != std::string::npos && pos2 != std::string::npos) { str_reqXml = strBuf.substr(pos1, pos2 - pos1 + 20); } std::ofstream outfile; outfile.open("reqXml.txt"); outfile << str_reqXml; outfile.close();
soap_body_end_in函數開始處添加以下代碼:
//接收完應答報文 獲取應答報文信息(作為客戶端的時候) std::string str_resXml = ""; std::string strBuf; std::string strEnd = "</SOAP-ENV:Envelope>"; std::string::size_type pos1 = std::string::npos; std::string::size_type pos2 = std::string::npos; pos1 = std::string::npos; pos2 = std::string::npos; soap->buf[SOAP_BUFLEN - 1] = '\0'; strBuf = soap->buf; pos1 = strBuf.find("<?xml", 0); pos2 = strBuf.find(strEnd, 0); if (pos1 != std::string::npos && pos2 != std::string::npos) { str_resXml = strBuf.substr(pos1, pos2 - pos1 + strEnd.length()); } std::ofstream outfile; outfile.open("resXml.txt"); outfile << str_resXml; outfile.close();
soap_recv_raw函數結尾處(return前)添加以下代碼:
//請求報文(作為服務端的時候) std::string req_data; req_data.assign(soap->buf, ret); std::ofstream outfile; outfile.open("req_data.txt"); outfile << req_data; outfile.close();
soap_flush_raw函數結尾處(return前)添加以下代碼:
//應答報文(作為服務端的時候) std::string res_data; res_data.assign(s, n); std::ofstream outfile; outfile.open("res_data.txt"); outfile << res_data; outfile.close();
註:客戶端可以一直打印報文,服務端隻能在Debug運行時才會打印報文,應該有其它方法可以解決。
SOAP測試
運行上面的服務器、客戶端項目,可以看到運行API調用結果,也可以使用SoapUI進行測試。
進入官網的下載鏈接:https://www.soapui.org/downloads/soapui/,下載開源版本並安裝。
打開軟件,在菜單欄的“File”中選擇“New SOAP Project”:
展開左側的項目,雙擊“Request”,開始進行測試:
項目源碼
SoapTest提取碼: 89mu
總結
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!
推薦閱讀:
- Python中WebService客戶端接口調用及身份驗證的問題
- Java面試題沖刺第十四天–PRC框架
- 遠程過程調用RPC基本概念及實現原理
- java調用WebService服務的四種方法總結
- 使用Postman和SoapUI工具測試WebService接口