預備知識
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 會產生相容性以及安全問題;防火牆和Proxy 伺服器通常會阻止此類流量。通過 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
[cpp]
view plaincopyprint?
- //gsoapopt cw
- //gsoap ns2 schema namespace: urn:add
- //gsoap ns2 schema form: unqualified
- //gsoap ns2 service name: add
- //gsoap ns2 service type: addPortType
- //gsoap ns2 service port:http://websrv.cs.fsu.edu/~engelen/addserver.cgi
- //gsoap ns2 service namespace: urn:add
- //gsoap ns2 service transport: http://schemas.xmlsoap.org/soap/http
- //gsoap ns2 service method-style: add rpc
- //gsoap ns2 service method-encoding: add http://schemas.xmlsoap.org/soap/encoding/
- //gsoap ns2 service method-action: add ""
- int ns2__add( int num1, int num2, int* sum );
//gsoapopt cw//gsoap ns2 schema namespace: urn:add//gsoap ns2 schema form: unqualified//gsoap ns2 service name: add//gsoap ns2 service type: addPortType//gsoap ns2 service port:http://websrv.cs.fsu.edu/~engelen/addserver.cgi//gsoap ns2 service namespace: urn:add//gsoap ns2 service transport: http://schemas.xmlsoap.org/soap/http//gsoap ns2 service method-style: add rpc//gsoap ns2 service method-encoding: add http://schemas.xmlsoap.org/soap/encoding///gsoap ns2 service method-action: add ""int ns2__add( int num1, int num2, int* sum );/*注意此處是兩條連續的底線,不然編譯找不到add.nsmap*/
3、產生代碼架構
我們執行一下命令,自動產生一些遠程調用需要的檔案。(先將他們加如到系統內容變數中)
soapcpp2 -c add.h
-c是產生純C代碼,如果提示找不到typemap.dat,將gsoap-2.8\gsoap下的typemap.dat複製到目前的目錄就可以了。通過上列命令我們會得到如下檔案:
先大概記住他們的名字,將來會提到他們。
4、添加服務端代碼,建立檔案:addserver.c
[cpp]
view plaincopyprint?
- #include "soapH.h"
- #include "add.nsmap"
-
- int main(int argc, char **argv)
- {
- int m, s;
- struct soap add_soap;
- soap_init(&add_soap);
- soap_set_namespaces(&add_soap, namespaces);
-
- if (argc < 2) {
- printf("usage: %s <server_port> \n", argv[0]);
- exit(1);
- } else {
- m = soap_bind(&add_soap, NULL, atoi(argv[1]), 100);
- if (m < 0) {
- soap_print_fault(&add_soap, stderr);
- exit(-1);
- }
- fprintf(stderr, "Socket connection successful: master socket = %d\n", m);
- for (;;) {
- s = soap_accept(&add_soap);
- if (s < 0) {
- soap_print_fault(&add_soap, stderr);
- exit(-1);
- }
- fprintf(stderr, "Socket connection successful: slave socket = %d\n", s);
- soap_serve(&add_soap);
- soap_end(&add_soap);
- }
- }
- return 0;
- }
- #if 1
- int ns2__add(struct soap *add_soap, int num1, int num2, int *sum)
- {
- *sum = num1 + num2;
- return 0;
- }
- #endif
#include "soapH.h"#include "add.nsmap"int main(int argc, char **argv){ int m, s; struct soap add_soap; soap_init(&add_soap); soap_set_namespaces(&add_soap, namespaces); if (argc < 2) { printf("usage: %s <server_port> \n", argv[0]); exit(1); } else { m = soap_bind(&add_soap, NULL, atoi(argv[1]), 100); if (m < 0) { soap_print_fault(&add_soap, stderr); exit(-1); } fprintf(stderr, "Socket connection successful: master socket = %d\n", m); for (;;) { s = soap_accept(&add_soap); if (s < 0) { soap_print_fault(&add_soap, stderr); exit(-1); } fprintf(stderr, "Socket connection successful: slave socket = %d\n", s); soap_serve(&add_soap); soap_end(&add_soap); } } return 0;}#if 1int ns2__add(struct soap *add_soap, int num1, int num2, int *sum){ *sum = num1 + num2; return 0;}#endif
5、添加用戶端代碼,建立檔案:addclient.c
[cpp]
view plaincopyprint?
- #include "soapStub.h"
- #include "add.nsmap"
-
- int add(const char *server, int num1, int num2, int *sum)
- {
- struct soap add_soap;
- int result = 0;
- soap_init(&add_soap);
- soap_set_namespaces(&add_soap, namespaces);
- soap_call_ns2__add(&add_soap, server, NULL, num1, num2, sum);
- printf("server is %s, num1 is %d, num2 is %d/n", server, num1, num2);
-
- if (add_soap.error) {
- printf("soap error: %d, %s, %s\n", add_soap.error, *soap_faultcode(&add_soap), *soap_faultstring(&add_soap));
- result = add_soap.error;
- }
- soap_end(&add_soap);
- soap_done(&add_soap);
- return result;
- }
#include "soapStub.h"#include "add.nsmap"int add(const char *server, int num1, int num2, int *sum){ struct soap add_soap; int result = 0; soap_init(&add_soap); soap_set_namespaces(&add_soap, namespaces); soap_call_ns2__add(&add_soap, server, NULL, num1, num2, sum); printf("server is %s, num1 is %d, num2 is %d/n", server, num1, num2); if (add_soap.error) { printf("soap error: %d, %s, %s\n", add_soap.error, *soap_faultcode(&add_soap), *soap_faultstring(&add_soap)); result = add_soap.error; } soap_end(&add_soap); soap_done(&add_soap); return result;}
6、寫用戶端測試代碼,建立檔案:addtest.c
[cpp]
view plaincopyprint?
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- int add(const char *server, int num1, int num2, int *sum);
- int main(int argc, char **argv)
- {
- int result = -1;
- char server[128] = {0};
- int num1;
- int num2;
- int sum;
-
- if (argc < 4) {
- printf("usage: %s <ip:port> num1 num2 \n", argv[0]);
- exit(1);
- }
-
- strcpy(server,argv[1]);
- num1 = atoi(argv[2]);
- num2 = atoi(argv[3]);
- result = add(server, num1, num2,&sum);
-
- if (result != 0) {
- printf("soap error, errcode=%d\n", result);
- } else {
- printf("%d + %d = %d\n", num1, num2, sum);
- }
- return 0;
- }
#include <stdio.h>#include <stdlib.h>#include <string.h>int add(const char *server, int num1, int num2, int *sum);int main(int argc, char **argv){ int result = -1; char server[128] = {0}; int num1; int num2; int sum; if (argc < 4) { printf("usage: %s <ip:port> num1 num2 \n", argv[0]); exit(1); } strcpy(server,argv[1]); num1 = atoi(argv[2]); num2 = atoi(argv[3]); result = add(server, num1, num2,&sum); if (result != 0) { printf("soap error, errcode=%d\n", result); } else { printf("%d + %d = %d\n", num1, num2, sum); } return 0;}
7、編寫Makefile,編譯前,先將gsoap-2.8\gsoap目錄下的stdsoap2.c和stdsoap2.h複製到目前的目錄下,它提供了對SOAP協議的簡單調用。
[cpp]
view plaincopyprint?
- GSOAP_ROOT = /root/onvif/gsoap-2.8/gsoap
- CC = gcc -g -DWITH_NONAMESPACES
- INCLUDE = -I$(GSOAP_ROOT)
- SERVER_OBJS = soapC.o stdsoap2.o soapServer.o addserver.o
- CLIENT_OBJS = soapC.o stdsoap2.o soapClient.o addclient.o addtest.o
-
- all: server
- server: $(SERVER_OBJS)
- $(CC) $(INCLUDE) -o addserver $(SERVER_OBJS)
-
- client: $(CLIENT_OBJS)
- $(CC) $(INCLUDE) -o addtest $(CLIENT_OBJS)
-
- clean:
- rm -f *.o addtest
GSOAP_ROOT = /root/onvif/gsoap-2.8/gsoapCC = gcc -g -DWITH_NONAMESPACESINCLUDE = -I$(GSOAP_ROOT)SERVER_OBJS = soapC.o stdsoap2.o soapServer.o addserver.o CLIENT_OBJS = soapC.o stdsoap2.o soapClient.o addclient.o addtest.oall: serverserver: $(SERVER_OBJS) $(CC) $(INCLUDE) -o addserver $(SERVER_OBJS) client: $(CLIENT_OBJS) $(CC) $(INCLUDE) -o addtest $(CLIENT_OBJS)clean:rm -f *.o addtest
8、編譯服務端make server,編譯用戶端make client 得到addserver和addtest
9、測試
一個最簡單的soap調用的例子完成了。
執行個體分析
服務端代碼
下面我們來分析上面的例子,剛才我們只是建立一個add.h標頭檔,在add.h標頭檔中聲明了一個函數:
[cpp]
view plaincopyprint?
- int ns2__add( int num1, int num2, int* sum );
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函數的調用