onvif規範的實現:使用gSOAP創建SOAP調用實例

預備知識

ONVIF規範中設備管理和控制部分所定義的接口均以Web Services的形式提供。ONVIF規範涵蓋了完全的XML及WSDL的定義。每一個支持ONVIF規範的終端設備均須提供與功能相應的Web Service。服務端與客戶端的數據交互採用SOAP協議。【來自http://blog.csdn.net/ghostyu】

ONVIF中的其他部分比如音視頻流則通過RTP/RTSP進行 。

那麼WebServices、SOAP、WSDL、gSOAP又都是什麼?

假如我們需要開發一個linux上的app,這個app需要與遠端的Web服務有一個交互,比如獲取一個運算結果、或者是天氣等,那麼我們就需要使用WebServices。

Web Services可以概述爲:

Web Services 可以將應用程序轉換爲網絡應用程序。
通過使用 Web Services,應用程序可以向全世界發佈信息,或提供某項功能。
Web Services 可以被其他應用程序使用。
通過 Web Services,會計部門的 Win 服務器可以與 IT 供應商的 UNIX 服務器相連接。
基本的 Web Services 平臺是 XML+HTTP。
Web services 使用 XML 來編解碼數據,並使用 SOAP 來傳輸數據。

SOAP又是什麼?

SOAP 是基於 XML 的簡易協議,可使應用程序在 HTTP 之上進行信息交換。或者更簡單地說:SOAP 是用於訪問網絡服務的協議。

對於應用程序開發來說,使程序之間進行因特網通信是很重要的。目前的應用程序通過使用遠程過程調用(RPC)在諸如 DCOM 與 CORBA 等對象之間進行通信,但是 HTTP 不是爲此設計的。RPC 會產生兼容性以及安全問題;防火牆和代理服務器通常會阻止此類流量。通過 HTTP 在應用程序間通信是更好的方法,因爲 HTTP 得到了所有的因特網瀏覽器及服務器的支持。SOAP 就是被創造出來完成這個任務的。SOAP 提供了一種標準的方法,使得運行在不同的操作系統並使用不同的技術和編程語言的應用程序可以互相進行通信

如何實現SOAP?

我們要知道SOAP協議是基於XML的,那麼如何能夠將他們嵌入到C/C++的應用程序裏使用?

gSOAP編譯工具就提供了一個SOAP/XML 關於C/C++ 語言的實現,從而讓C/C++語言開發web服務或客戶端程序的工作變得輕鬆了很多。將與開發無關的SOAP協議的實現細節相關的內容對開發人員隱藏起來。因爲SOAP提供的是一種標準化的方法,gSOAP的編譯器能夠自動的將用戶定義的本地化的C或C++數據類型轉變爲符合XML語法的數據結構,這樣,只用一組簡單的API就將用戶從SOAP細節實現工作中解脫了出來,可以專注與應用程序邏輯的實現工作了。並且可以跨越多個操作系統、語言環境以及在防火牆後的不同組織。

更直白的說,使用gSOAP可以產生用於開發Web Services的SOAP通信協議方面的代碼框架,開發人員只需要實現server的被調用的函數,然後在client端就可以像調用本地函數一樣調用在遠端的函數。gSOAP包含兩個工具wsdl2h和soapcpp2,用來產生代碼框架。

開發Web服務程序,需使用gSOAP生成服務器端和客戶端代碼框架(通常情況下之需要實現server端或者實現client,因爲另一端通常是別人做好的,比如ipnc中的onvif,實現的server端)。我們有兩種做法:

編寫WSDL,使用wsdl2h生成頭文件,再soapcpp2生成框架代碼;
編寫頭文件,使用soapcpp2生成框架代碼;
這兩種方式,結果是一樣的,最終都有產生頭文件,並生成代碼。不同在於,在項目的開發中需要維護的文件不同,前者是需要維護WSDL文件,後者維護頭文件。

SOAP調用示例

下面就使用第二種方法來實現一個簡單的通信實例:在遠端實現兩數相加,然後返回運算結果。

1、下載gSOAP

我使用的版本時2.8.8,http://www.kuaipan.cn/file/id_48923272389088693.htm

gSOAP-2.8軟件包不需要安裝,直接解壓,在gsoap-2.8\gsoap\bin目錄下是上面提到的兩個命令行工具,包含win32、linux、maxOS等三種版本,在使用soapcpp2生產代碼框架時一般需要gsoap-2.8\gsoap\import目錄下和gsoap-2.8\gsoap\custom的 文件。在命令行中使用-I<PATH>包含進來即可。

2、編寫頭文件:add.h

 在這裏我們不需要wsdl的文件,可以直接從.h文件來生成代碼。我們定義一個函數聲明文件,用來定義接口函數,名稱爲add.h

  1. //gsoapopt cw  
  2. //gsoap ns2 schema namespace: urn:add  
  3. //gsoap ns2 schema form: unqualified  
  4. //gsoap ns2 service name: add  
  5. //gsoap ns2 service type: addPortType  
  6. //gsoap ns2 service port:http://websrv.cs.fsu.edu/~engelen/addserver.cgi  
  7. //gsoap ns2 service namespace: urn:add  
  8. //gsoap ns2 service transport: http://schemas.xmlsoap.org/soap/http  
  9. //gsoap ns2  service method-style:      add rpc  
  10. //gsoap ns2  service method-encoding:   add http://schemas.xmlsoap.org/soap/encoding/  
  11. //gsoap ns2  service method-action:     add ""  
  12. int ns2__add( int num1, int num2, int* sum );  
3、產生代碼框架

我們執行一下命令,自動生成一些遠程調用需要的文件。(先將他們加如到系統環境變量中)

soapcpp2 -c add.h

-c是產生純C代碼,如果提示找不到typemap.dat,將gsoap-2.8\gsoap下的typemap.dat複製到當前目錄就可以了。通過上列命令我們會得到如下文件:


先大概記住他們的名字,將來會提到他們。

4、添加服務端代碼,創建文件:addserver.c

  1. #include "soapH.h"  
  2. #include "add.nsmap"  
  3.   
  4. int main(int argc, char **argv)  
  5. {  
  6.     int m, s;  
  7.     struct soap add_soap;  
  8.     soap_init(&add_soap);  
  9.     soap_set_namespaces(&add_soap, namespaces);  
  10.   
  11.     if (argc < 2) {  
  12.         printf("usage: %s <server_port> \n", argv[0]);  
  13.         exit(1);  
  14.     } else {  
  15.         m = soap_bind(&add_soap, NULL, atoi(argv[1]), 100);  
  16.         if (m < 0) {  
  17.             soap_print_fault(&add_soap, stderr);  
  18.             exit(-1);  
  19.         }  
  20.         fprintf(stderr, "Socket connection successful: master socket = %d\n", m);  
  21.         for (;;) {  
  22.             s = soap_accept(&add_soap);  
  23.             if (s < 0) {  
  24.                 soap_print_fault(&add_soap, stderr);  
  25.                 exit(-1);  
  26.             }  
  27.             fprintf(stderr, "Socket connection successful: slave socket = %d\n", s);  
  28.             soap_serve(&add_soap);  
  29.             soap_end(&add_soap);  
  30.         }  
  31.     }  
  32.     return 0;  
  33. }  
  34. #if 1  
  35. int ns2__add(struct soap *add_soap, int num1, int num2, int *sum)  
  36. {  
  37.     *sum = num1 + num2;  
  38.     return 0;  
  39. }  
  40. #endif  
5、添加客戶端代碼,創建文件:addclient.c

  1. #include "soapStub.h"  
  2. #include "add.nsmap"  
  3.   
  4. int add(const char *server, int num1, int num2, int *sum)  
  5. {  
  6.     struct soap add_soap;  
  7.     int result = 0;  
  8.     soap_init(&add_soap);  
  9.     soap_set_namespaces(&add_soap, namespaces);  
  10.     soap_call_ns2__add(&add_soap, server, NULL, num1, num2, sum);  
  11.     printf("server is %s, num1 is %d, num2 is %d/n", server, num1, num2);  
  12.   
  13.     if (add_soap.error) {  
  14.         printf("soap error: %d, %s, %s\n", add_soap.error, *soap_faultcode(&add_soap), *soap_faultstring(&add_soap));  
  15.         result = add_soap.error;  
  16.     }  
  17.     soap_end(&add_soap);  
  18.     soap_done(&add_soap);  
  19.     return result;  
  20. }  
6、寫客戶端測試代碼,創建文件:addtest.c

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4.   
  5. int add(const char *server, int num1, int num2, int *sum);  
  6. int main(int argc, char **argv)  
  7. {  
  8.     int result = -1;  
  9.     char server[128] = {0};  
  10.     int num1;  
  11.     int num2;  
  12.     int sum;  
  13.   
  14.     if (argc < 4) {  
  15.         printf("usage: %s <ip:port> num1 num2 \n", argv[0]);  
  16.         exit(1);  
  17.     }  
  18.   
  19.     strcpy(server,argv[1]);  
  20.     num1 = atoi(argv[2]);  
  21.     num2 = atoi(argv[3]);  
  22.     result = add(server, num1, num2,&sum);  
  23.   
  24.     if (result != 0) {  
  25.         printf("soap error, errcode=%d\n", result);  
  26.     } else {  
  27.         printf("%d + %d = %d\n", num1, num2, sum);  
  28.     }  
  29.     return 0;  
  30. }  
7、編寫Makefile,編譯前,先將gsoap-2.8\gsoap目錄下的stdsoap2.c和stdsoap2.h複製到當前目錄下,它提供了對SOAP協議的簡單調用。

  1. GSOAP_ROOT = /root/onvif/gsoap-2.8/gsoap  
  2. CC = gcc -g -DWITH_NONAMESPACES  
  3. INCLUDE = -I$(GSOAP_ROOT)  
  4. SERVER_OBJS = soapC.o stdsoap2.o soapServer.o addserver.o   
  5. CLIENT_OBJS = soapC.o stdsoap2.o soapClient.o addclient.o addtest.o  
  6.   
  7. all: server  
  8. server: $(SERVER_OBJS)   
  9.     $(CC) $(INCLUDE) -o addserver $(SERVER_OBJS)   
  10.   
  11. client: $(CLIENT_OBJS)   
  12.     $(CC) $(INCLUDE) -o addtest $(CLIENT_OBJS)  
  13.   
  14. clean:  
  15.     rm -f *.o addtest  
8、編譯服務端make server,編譯客戶端make client 得到addserver和addtest

9、測試



一個最簡單的soap調用的例子完成了。

實例分析

服務端代碼

下面我們來分析上面的例子,剛纔我們只是創建一個add.h頭文件,在add.h頭文件中聲明瞭一個函數:

  1. int ns2__add( int num1, int num2, int* sum );  
其他所有的的代碼都是一句他來生成的。那麼這個的實體在哪?對,就是在需要我們自己添加的addserver.c中:


但是它好像多了一個struct soap類型的參數,這是soap全局運行環境,所有的函數都第一個包含這個參數。注意上面的Makefile,不管是編譯server還是編譯client都是沒有用到剛纔的add.h文件的。ns2__add真正的聲明在自動產生的soapStub.h中


然後在自動產生的soapServer.c中被soap_serve_ns2__add()函數調用。這樣,就將真正的加法運算的ns2__add函數和soap代碼框架聯繫了起來。那麼,在客戶端的代碼中又是怎樣來調用這個遠程函數的呢?

客戶端代碼

在剛纔添加的addtest.c中main函數中調用一個簡單的add函數


這個函數的實現也是我們自己添加的,在addclient.c中:


這個函數有些複雜,因爲它把客戶端的調用和soap聯繫了起來,還記得嗎,我們編譯server和client的時候複製了兩個文件stdsoap2.h和stdsoap2.c,這裏面的soap_init() soap_end()等函數來自他們。stdsoap2提供了soap協議的簡單操作,之需要簡單的函數調用就能完成遠程的函數調用。注意soap_call_ns2__add(),它同樣在soapStub.h中聲明,只不過是Client-Side Call Stubs,不明白stub意思的可以搜索rpc


這個函數的實現在自動產生的soapClient.c源文件中。同樣不需要我們實現。

這樣就可以通過調用gSOAP提供的stdsoap2的soap_init和自動產生的soap_call_ns2__add就實現了遠程主機上的ns2__add函數的調用


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