預備知識
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
[cpp] view plain copy print ?
- //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 );
3、産生代碼架構
我們執行一下指令,自動生成一些遠端調用需要的檔案。(先将他們加如到系統環境變量中)
soapcpp2 -c add.h
-c是産生純C代碼,如果提示找不到typemap.dat,将gsoap-2.8\gsoap下的typemap.dat複制到目前目錄就可以了。通過上列指令我們會得到如下檔案:

先大概記住他們的名字,将來會提到他們。
4、添加服務端代碼,建立檔案:addserver.c
[cpp] view plain copy print ?
- #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 1
int ns2__add(struct soap *add_soap, int num1, int num2, int *sum)
{
*sum = num1 + num2;
return 0;
}
#endif
5、添加用戶端代碼,建立檔案:addclient.c
[cpp] view plain copy print ?
- #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 plain copy print ?
- #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 plain copy print ?
- 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/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
8、編譯服務端make server,編譯用戶端make client 得到addserver和addtest
9、測試
一個最簡單的soap調用的例子完成了。
執行個體分析
服務端代碼
下面我們來分析上面的例子,剛才我們隻是建立一個add.h頭檔案,在add.h頭檔案中聲明了一個函數:
[cpp] view plain copy print ?
- 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函數的調用