用c++實現webservice服務

最近自己在學習onvif攝像頭相關的協議,然後就開始學習了下webservice服務,然後整理下自己對它的理解,以及參考網上的一些教程,實現了webservice服務。

WebService介紹

首先我們來談一下爲什麼需要學習webService這樣的一個技術吧…

問題一

如果我們的網站需要提供一個天氣預報這樣一個需求的話,那我們該怎麼做?????

天氣預報這麼一個功能並不是簡單的JS組件就能夠實現的,它的數據是依賴數據庫分析出來的,甚至需要衛星探測…我們個人建站是不可能搞這麼一個數據庫的吧。

那麼既然我們自己幹不了,我們可以去找別人嗎???我們從搜索引擎搜索,可以發現很多提供天氣預報的網站,但是它返回的是一個網頁,而我們僅僅需要的是對應的數據

我們可能就在想,我們能不能僅僅只要它返回的數據,而並不是經過加工處理後返回的網頁呢??

於是乎,webService就誕生了,webservice就是一個部署在Web服務器上的,它向外界暴露出一個能夠通過Web進行調用的API。也就是說:當我們想要獲取天氣預報的信息,我們可以調用別人寫好的service服務,我們調用就能夠得到結果了

問題二

可是我們寫網站主流的就有好幾個平臺:Java、.net、PHP等等,那麼部署在Web服務器上的服務器也就是webserice怎麼能夠就讓我們不同的平臺都能夠調用呢??

我們知道java、.net這樣的平臺他們語言的基本數據類型、複雜數據類型就可能不一樣,那麼怎麼能夠實現調用的呢???

來引用一段話

大家在寫應用程序查詢數據庫時,並沒有考慮過爲什麼可以將查詢結果返回給上層的應用程序,甚至認爲,這就是數據庫應該做的,其實不然,這是數據庫通過TCP/IP協議與另一個應用程序進行交流的結果,而上層是什麼樣的應用程序,是用什麼語言,數據庫本身並不知道,它只知道接收到了一份協議,這就是SQL92查詢標準協議。

無論是Java、.net、PHP等等的平臺,只要是網頁開發都是可以通過http協議來進行通信的,並且返回的數據要是通用的話,那麼我們早就學過這樣的一種技術【XML】

所以webservice實際上就是http+XML

對webservice的理解

WebService,顧名思義就是基於Web的服務。它使用Web(HTTP)方式,接收和響應外部系統的某種請求。從而實現遠程調用.

我們可以調用互聯網上查詢天氣信息Web服務,然後將它嵌入到我們的程序(C/S或B/S程序)當中來,當用戶從我們的網點看到天氣信息時,他會認爲我們爲他提供了很多的信息服務,但其實我們什麼也沒有做,只是簡單調用了一下服務器上的一段代碼而已。

學習WebService可以將你的服務(一段代碼)發佈到互聯網上讓別人去調用,也可以調用別人機器上發佈的WebService,就像使用自己的代碼一樣.。

webService相關術語

  • 名詞1:XML. Extensible Markup Language -擴展性標記語言
    • XML,用於傳輸格式化的數據,是Web服務的基礎。
    • namespace-命名空間。
    • xmlns=“http://itcast.cn” 使用默認命名空間。
    • xmlns:itcast=“http://itcast.cn”使用指定名稱的命名空間。
  • 名詞2:WSDL – WebService Description Language – Web服務描述語言。
    • 通過XML形式說明服務在什麼地方-地址。
    • 通過XML形式說明服務提供什麼樣的方法 – 如何調用。
  • 名詞3:SOAP-Simple Object Access Protocol(簡單對象訪問協議)
    • SOAP作爲一個基於XML語言的協議用於有網上傳輸數據。
    • SOAP = 在HTTP的基礎上+XML數據。
    • SOAP是基於HTTP的。
    • SOAP的組成如下:
      • Envelope – 必須的部分。以XML的根元素出現。
      • Headers – 可選的。
      • Body – 必須的。在body部分,包含要執行的服務器的方法。和發送到服務器的數據。

對於c++來說我們發佈webservice服務,也是有庫支撐的,那就是gsoap,我用的gsoap的版本是

 GSoap是一個開源的工具,功能非常強大,各位可以到網絡上搜索學習看看這個不錯的工具,gSOAP編譯工具提供了一個SOAP/XML關於C/C++語言的實現,從而讓C/C++語言開發web服務或客戶端程序的工作變得輕鬆了很多。絕大多數的C++web服務工具包提供一組API函數類庫來處理特定的SOAP數據結構,這樣就使得用戶必須改變程序結構來適應相關的類庫。這裏我主要通過C/S模式調用GSOAP來實現服務端和客戶端。首先我的目標是提供一組簡單數學的接口:加減乘除四個接口。

   在使用GSOAP提供幾個接口之前,首先帶大家熟悉一下Windows下GSOAP裏面攜帶的兩個工具:wsdl2h.exe以及soapcpp2.exe,兩工具可以在下載的gsoap2.8.29(下載可以到開源社區去下載:http://sourceforge.net/projects/gsoap2)的gsoap/bin/win32目錄找到,當然這是gsoap在windows提供的兩個可直接的工具,下面我們先來認識一下這兩個簡單易用的工具:

wsdl2h工具: 正如其名wsdl到h,也就是webservice description language(.wsdl文件)轉換成.h的頭文件工具,換句話說就是用來轉爲.h的工具,這裏我們不介紹wsdl文件如何編寫,也不介紹wsdl轉h功能,因爲我們自己已經寫好的.h文件,所以用不到,如果想了解,請到onvif官網下載相關wsdl然後用這個工具試試!具體功能如下:

(1)、它僅僅負責生成wsdl中描述的相應的頭文件,這些頭文件不能直接使用,需要經過soapcpp2.exe轉換爲gsoap生成的接口後才能使用

(2)、輸入條件爲一個或多個wsdl或xsd文件或者相應的URL路徑,如果輸入wsdl或xsd則輸出爲第一個名稱的.h文件,如果爲URL則默認爲標準輸出

攜帶參數:

-s: don't generate STL code 也就是不使用標準模板庫,如果不使用-s參數那麼默認使用STL庫,也就是必須將以來的文件./gsoap/stdsoap2.h和stdsoap2.cpp拷貝至應用的程序當中。

-c: generate C source code c意思是生成c文件接口,默認是c++頭文件

-o:output to file 指定輸出文件名稱,可以重命名生成的文件名稱

-t:use type map file instead of the default file typemap.dat 使用類型定義文件,可以在typemap.dat(自帶的)中修改生成選項,生成符合要求的頭文件,如編碼

當然,該用具還有其他n個參數使用,此處僅僅介紹幾個常用的,其他控制參數自己去了解學習。

soapcpp2.exe:該工具功能是將我們提供或有wsdl2h生成的.h的頭文件(接口)轉換爲gsoap提供的形式的接口和rpc代理相關的代碼和框架(當然我沒有具體去了解,vs中你可以搜索到攜帶相應接口名的其他代理方法並且有相應的實現)

相關參數如下:

(1)、-1生成SOAP1.1協議的代碼

(2)、-2生成SOAP1.2協議的代碼

(3)、-C只是生成客戶端相關的代碼

(4)、-S只是生成服務端相關的代碼

(5)、-i generate C++ service proxies and objects inherited from soap struct 生成對象爲soap的子類,會影響生成文件的使用方法。如 class SOAP_CMAC BasicCMIRPService : public soap

其他參數此處不再介紹,如需要更詳細解釋,請到官網查詢相應工具使用介紹。

        好,學習兩個工具之後,我們就可以將以上工具派上用場了,當然我們這裏不需要wsdl2h工具,因爲我們是C++程序員,頭文件我們自己編寫提供即可,不需要編寫wsdl在轉爲.h文件,省去了不少麻煩,我們直接使用soapcpp2工具將頭文件轉對應的頭即可,首先服務端提供加減乘除4個接口,客戶端調用加減乘除4個接口,約定接口如下:

int Add(int nNum1, int nNum2, int* pResult);

int Sub(int nNum1, int nNum2, int* pResult);

int Mul(int nNum1, int nNum2, int* pResult);

int Div(int nNum1, int nNum2, int* pResult);

返回值我們以參數地址形式提供出去而不是以函數返回值形式提供跟gsoap生成接口有關,它生成接口調用的時候返回值表示傳入參數是否正確或調用成功用到了返回值,所以我們自己的接口爲了不覆蓋它的使用得這麼寫,當然,形式變化而已,無所謂!既然客戶端和服務器都以改4接口爲通信接口,那麼這4個接口就作爲我們要轉換爲gsoap代理或框架時soapcpp2用到的頭文件的聲明,新建服務端程序,新建win32工程項目:GSoapServer空項目,添加類GSoapServer,然後刪除該類所有聲明和實現(目的就是用GSaopServer.h和GSaopServer.cpp兩個配對文件,不需要相應的類),在GSoapServer.h中聲明以上幾個函數:

#pragma once

int Add(int nNum1, int nNum2, int* pResult);

int Sub(int nNum1, int nNum2, int* pResult);

int Mul(int nNum1, int nNum2, int* pResult);

int Div(int nNum1, int nNum2, int* pResult);


在GSoapServer.cpp中添加main函數

#include "GSoapServer.h"

int main(int argc, char** argv)

{

return 0;

}

編譯該項目,保證編譯通過無錯誤,接下來見gsoap2.8工具目錄下的./gsoap/bin/win32/gsoapcpp2.exe 拷貝至與GSoapServer.h相同目錄下,使用命令行cmd,進入該目錄

如:

鍵入:cd E:/project/C++/test/GSoapServer/GSoapServer

鍵入:E:

使用soapcpp2.exe: soapcpp2.exe  GSoapServer.h

此時,由於我們沒有指定-C或-S,它會爲我們生成客戶端和服務端的所有代碼,如果只是生成客戶端代碼添加-C參數,此時會生成soapH.c、soapC.cpp、soapStub.h、soapClient.cpp soapClientLib.cpp代碼(加上我們以來的stl庫stdsoap2.h和stdsoap2.cpp供7個文件,當然包含被轉的GSoapServer.h文件就是8個,此處生成後要不要GSaopServer.h已經無所謂了);如果只想生成服務端的代碼,那麼添加-S參數,此時會生成soapH.c、soapC.cpp、soapStub.h、soapServer.cpp soapServerLib.cpp以及soap.nsmap文件,也就是8個文件(不計算GsopServer.h被轉換的頭文件)

其中soap.h和soapC.cpp是具體的實現,soapStub.h爲代理的聲明(服務端我們需要實現聲明的4個接口-該四個接口和我們約定的不太一樣,第一個參數多了

soap*對象指針)

對於服務端,我們將soap.h soapC.cpp soapStub.h soapServer.cpp以及依賴的stl庫stdsoap2.h和stdsoap2.cpp 五個文件添加的項目中(soapServerLib.cpp不需要,否則會出錯),添加後所有的cpp屬性設置預編譯頭爲“不使用預編譯頭”,最後找到soapStub.h文件最後幾行會發現我們約定的即可接口聲明(接口稍微有變化,多了soap*參數),拷貝到GSoapServer.cpp工程目錄的main函數之前,並且實現之:

/** Web service operation 'Add' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Add(struct soap*, int nNum1, int nNum2, int *pResult)
{
*pResult = nNum1 + nNum2;
return SOAP_OK;
}


/** Web service operation 'Sub' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Sub(struct soap*, int nNum1, int nNum2, int *pResult)
{
*pResult = nNum1 - nNum2;
return SOAP_OK;
}


/** Web service operation 'Mul' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Mul(struct soap*, int nNum1, int nNum2, int *pResult)
{
*pResult = nNum1 * nNum2;
return SOAP_OK;
}


/** Web service operation 'Div' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Div(struct soap*, int nNum1, int nNum2, int *pResult)
{
if(0 == nNum2)
{
*pResult = 0;
return -1;
}
*pResult = nNum1 / nNum2;
return SOAP_OK;
}

編譯,發現沒有錯誤,如果有預編譯錯誤,設置cpp屬性去掉預編譯頭即可,切記不需要soapServerLib.cpp生成的文件,soapServer.cpp是必要的,它實現了soap_serve服務,也就是我們服務器提供的服務!

具體的服務器main函數實現如下:

#include "GSoapServer.h"

#include “soap.nsmap” // 服務端與客戶端必須包含的文件

/** Web service operation 'Add' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Add(struct soap*, int nNum1, int nNum2, int *pResult)
{
    *pResult = nNum1 + nNum2;
    return SOAP_OK;
}

/** Web service operation 'Sub' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Sub(struct soap*, int nNum1, int nNum2, int *pResult)
{
    *pResult = nNum1 - nNum2;
    return SOAP_OK;
}

/** Web service operation 'Mul' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Mul(struct soap*, int nNum1, int nNum2, int *pResult)
{
    *pResult = nNum1 * nNum2;
    return SOAP_OK;
}


/** Web service operation 'Div' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 Div(struct soap*, int nNum1, int nNum2, int *pResult)
{
    if(0 == nNum2)
    {
    *pResult = 0;
    return -1;
}
*pResult = nNum1 / nNum2;
return SOAP_OK;
}


int main(int argc, char**argv)
{
    struct soap math_service;
    soap_init(&math_service);
    // bind端口返回SOCKET套接字-雷同socket套接口函數服務器監聽過程
if(soap_bind(&math_service, NULL, 8686, 100) <0)
{
    soap_print_fault(&math_service, stderr);
    return -1;
}
fprintf(stderr, "start math webservice ...\n");
// 當然這裏可以用多個soap在多個現成中使用soap_new創建新的soap,每個線程一個
while(true)
{
    int nClient = (int)soap_accept(&math_service);

    if(nClient < 0)
    {
        soap_print_fault(&math_service, stderr);
        return -1;
    }
    fprintf(stderr, "client[%d] connect success. ..\n, nClient);
    soap_serve(&math_service);// provide service
    soap_end(&math_service);// end service
}
return 0;
}

啓動服務端程序,打開IE輸入:http://localhost:8686/

會發現IE返回:

<SOAP-ENV:Fault 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">

<faultcode>at source</faultcode>

<faultstring>HTTP GET method not implemented</faultstring>

</SOAP-ENV:Fault>

說明,服務端啓動成功!

接下來寫客戶端程序:

客戶端很簡單,因爲所有的文件我們已經用soapcpp2.exe工具生成了,新建GSoapClient工程,空工程或默認的win32控制檯都可以,將服務端生成的文件soapH.h soapStub.h stdsoap2.h以及對應的源文件soapC.cpp soapClient.cpp stdsoap2.cpp 拷貝至客戶端工程目錄下並添加文件至項目中,然後設置cpp對應的屬性,去掉預編譯頭文件,包含soap.nsmap文件,實現並調用webservice接口如下:

// GSoapClient.cpp : 定義控制檯應用程序的入口點。

//

#include "stdafx.h"
#include <stdlib.h>
#include "soap.nsmap"
int main(int argc, char** argv)
{
    struct soap math_soap;
    soap_init(&math_soap);
    int nResult = 0;
    const char* pWebService = "http://127.0.0.1:8686";
    soap_call_Add(&math_soap,  pWebService, "", 60, 30, &nResult);
if (math_soap.error)
{
    soap_print_fault(&math_soap, stderr);
}
printf("webservice:60+30=%d\n", nResult);
soap_call_Sub(&math_soap,  pWebService, "", 60, 30, &nResult);
if (math_soap.error)
{
    soap_print_fault(&math_soap, stderr);
}
printf("webservice:60-30=%d\n", nResult);
soap_call_Mul(&math_soap,  pWebService, "", 60, 30, &nResult);
if (math_soap.error)
{
    soap_print_fault(&math_soap, stderr);
}
printf("webservice:60*30=%d\n", nResult);
soap_call_Div(&math_soap,  pWebService, "", 60, 30, &nResult);
if (math_soap.error)
{
    soap_print_fault(&math_soap, stderr);

}
printf("webservice:60/30=%d\n", nResult);
soap_end(&math_soap);
soap_done(&math_soap);
system("pause");
return 0;
}

服務器啓動後,啓動客戶端,客戶端可以進行顯示相關的輸出結果,但是最後還是有點問題沒有弄清楚,就是客戶端可以調用我的服務,但是需要我給對方一個.h文件,然後對方通過gsoap來生成客戶端程序,然後通過代理進行調用,現在還不能通過工具生成對應的wsdl文件,讓對方直接訪問我的網址就得到wsdl,然後生成.h,再進行上面的操作,希望有能夠實現的下面留言回答下,

大家共同學習,本文主要參考兩篇文章得到

https://blog.csdn.net/sunxiaopengsun/article/details/77008132

https://www.imooc.com/article/25537?block_id=tuijian_wz

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章