第一節 Gsoap使用
SOAP 是基于 XML 的簡易協定,可使應用程式在 HTTP 之上進行資訊交換。或者更簡單地說:SOAP 是用于通路網絡服務的協定。
1.1簡介
ü gSOAP是一個跨平台的,用于開發Web Service服務端和用戶端的工具。
ü 在Windows、Linux、MAC OS和UNIX下使用C和C++語言編碼,內建了SSL功能。
ü 下載下傳位址:http://sourceforge.net/projects/gsoap2
ü 官方網站:http://genivia.com/Products/gsoap/index.html
1.2 window下使用gsoap開發webservice
對于Windows平台下開發用戶端:
首先下載下傳最新的gsoap_win32_2.7.6c.zip包。
下載下傳位址:http://optusnet.dl.sourceforge.net/sourceforge/gsoap2/gsoap_win32_2.7.6c.zip
檢視gsoap的User's Guide,基本就能對gsoap有個全面的了解。
gSOAP簡單多線程伺服器程式http://blog.chinaunix.net/u1/55091/showart_430965.html
純C gSoap實作WebService :http://hi.baidu.com/2sky2sea/blog/item/40ec5555680279c1b745ae9b.html
VC下用gsoap編寫webService和用戶端程式
ü 伺服器端(實作四則運算)
l 編寫add.h頭檔案
如:int ns__add(int num1, int num2, int* sum);
l 使用gsoap/bin目錄下的soapcpp2.exe程式編譯頭檔案。
把soapcpp2.exe拷貝到add.h目錄下,用cmd執行soapcpp2.exeadd.h。
( 編譯頭檔案并生成xml等檔案。其中,soapH.h andsoapC.cpp包含了資料類型的描述,soapClient.cpp給用戶端使用,soapServer.cpp給服務端使用。)
l 建立一個win32控制台工程,加入wsock32.lib庫,将剛才生成的那些檔案添加到工程中。然後編寫webserver.cpp主程式:
編譯時将gsoap_win32目錄下stdsoap2.cpp,stdsoap2.h檔案加入工程。
#include "add.h"
#include "add.nsmap"
#include ”stdsoap2.h”
int main(intargc, char* argv[])
{
int m, s;
struct soap add_soap; //定義結構體struct soap的對象add_soap
soap_init(&add_soap); //初始化add_soap對象
//soap_set_namespaces(&add_soap,add_namespaces);
if (argc < 2)
{
printf("usage: %s \n", argv[0]);
exit(1);
}
else
{
//啟動時,輸入soap監聽的端口atoi(argv[1]),将add_soap與端口綁定(TCP連接配接)
m = soap_bind(&add_soap, NULL, atoi(argv[1]),100);
if (m < 0)
{
soap_print_fault(&add_soap, stderr);
exit(-1);
}
fprintf(stderr, "Socket connectionsuccessful: master socket = %d\n", m);
for ( ; ; ) //循環等待監聽
{
s = soap_accept(&add_soap); //等待并接受用戶端的連接配接
if (s < 0)
{
soap_print_fault(&add_soap,stderr);
exit(-1);
}
fprintf(stderr, "Socketconnection successful: slave socket = %d\n", s);
soap_serve(&add_soap);//該句說明該server的服務
soap_end(&add_soap);
}
}
return 0;
}
//server端的實作函數與add.h中聲明的函數相同,但是多了一個目前的soap連接配接的參數
int ns__add(struct soap *add_soap, int num1, int num2, int*sum)
{
*sum = num1 + num2;
return 0;
}
ü 用戶端
#include
#include “stdsoap2.h”
#include "soapH.h"
#include "add.nsmap"
int add(const char* server,int num1, int num2, int *sum); //聲明調用函數server是webservice的位址
int main(int argc, char **argv)
{
int result = -1;
char* server="http://localhost:4567"; //webservice的伺服器位址
int num1 = 0;
int num2 = 0;
int sum = 0;
if( argc < 3 )
{
printf("usage: %s num1 num2 \n", argv[0]);
exit(0);
}
num1 = atoi(argv[1]);
num2 = atoi(argv[2]);
result = add(server, num1, num2, &sum); //調用webservice接口
if (result != 0)
{
printf("soap err,errcode =%d\n", result);
}
else
{
printf("%d+%d=%d\n", num1,num2, sum );
}
return 0;
}
//調用webservice接口通信
int add( const char* server, int num1, int num2, int *sum )
{
struct soap add_soap; //建立soap對象
int result = 0;
soap_init(&add_soap); //初始化soap對象
// soap_set_namespaces(&add_soap,add_namespaces);
//該函數是用戶端調用的主要函數,後面幾個參數和add.h中聲明的一樣,
//前面多了3個參數,函數名是接口函數名ns__add前面加上soap_call_
soap_call_ns__add( &add_soap,server, "", num1, num2, sum );
if(add_soap.error)
{
printf("soaperror:%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;
}
1.3 gsoap工具
1.3.1soapcpp2.exe
執行個體:指令行執行soapcpp2.exe add.h
生成:add.nsmap soapH.h soapC.c soapStub.h soapClient.c soapServer.c soapClientlib.c soapServerlib.c
add.add.req.xml add.add.res.xml
生成webservice的代碼架構。
功能
編譯頭檔案并生成源檔案、xml檔案等。其中,soapH.h和 soapC.cpp包含了資料類型的描述,soapClient.cpp給用戶端使用,soapServer.cpp給服務端使用。
用法
soapcpp2 [選項] 頭檔案
[選項]
-i 生成server的proxy和object,這種object繼承于soap struct。
-C 僅生成用戶端client代碼
-S 僅生成服務端server代碼
-x 不生成xml檔案。不用此項的話,将對頭檔案中定義的每個operation生成一個描述性的xml檔案
-L 不生成soapClientLib檔案和soapServerLib檔案
-p name 修改檔案名字首,代替soap
-q name 指定代理類和對象使用的名空間name,包含檔案名字首
1.3.2wsdl2h.exe
功能
ü 利用WSDL和XML schemas生成包含WS屬性和操作的C++風格gSoap頭檔案。
ü wsdl2h.exe用作wsdl和.h檔案的轉換,生成頭檔案。
使用方法
wsdl2h.exe [選項] wsdl檔案的url
選項:
-o filename.h 将wsdl轉化為filename.h頭檔案。
-s 不生成STL代碼
-c 生成純C風格的頭檔案,這将去除C++的一些特性
-n name 使用name代替預設字首ns
-t filename.dat 使用filename.dat代替預設的typemap.dat檔案
-zX 相容之前的X版本
1.3.3執行個體
ü 使用wsdl2h.exe生成calc.wsdl的頭檔案calc.h
wsdl2h -o calc.h http://www.genivia.com/calc.wsdl
ü 使用soapcpp2.exe生成calc.h對應的xml檔案及webservice相關的頭檔案及源檔案
soapcpp2 -i -C calc.h
第二節 使用Gsoap生成ONVIF架構
ü Onvif的架構生成是實作onvif協定的第一步,當然也可不用工具生成架構,完全按照協定來自己實作。
2.1 ONVIF架構生成
工具及平台
Gsoap版本:Gsoap2.8.27 http://sourceforge.net/projects/gsoap2/files/gSOAP/
Onvif :WSDL文檔(2015.12.15最新文檔)
架構代碼生成:windows下wsdl2、soapcpp2
步驟
ü 使用wsdl2h生成onvif.h,這裡使用線上的方式,免得還要手動添加好多.xsd檔案
wsdl2h –o onvif.h -c -s -t typemap.dat \
http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl
http://www.onvif.org/onvif/ver10/events/wsdl/event.wsdl
http://www.onvif.org/onvif/ver10/display.wsdl
http://www.onvif.org/onvif/ver10/deviceio.wsdl
http://www.onvif.org/onvif/ver20/imaging/wsdl/imaging.wsdl
http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl
http://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl
http://www.onvif.org/onvif/ver10/receiver.wsdl
http://www.onvif.org/onvif/ver10/recording.wsdl
http://www.onvif.org/onvif/ver10/search.wsdl
http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl
http://www.onvif.org/onvif/ver10/replay.wsdl
http://www.onvif.org/onvif/ver20/analytics/wsdl/analytics.wsdl
http://www.onvif.org/onvif/ver10/analyticsdevice.wsdl
http://www.onvif.org/onvif/ver10/schema/onvif.xsd
http://www.onvif.org/ver10/actionengine.wsdl
http://www.onvif.org/ver10/pacs/accesscontrol.wsdl
http://www.onvif.org/ver10/pacs/doorcontrol.wsdl
http://www.onvif.org/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl
http://www.onvif.org/ver10/accessrules/wsdl/accessrules.wsdl
http://www.onvif.org/ver10/credential/wsdl/credential.wsdl
http://www.onvif.org/ver10/schedule/wsdl/schedule.wsdl
ü 在onvif.h中加入#import “wsse.h”,用來做安全驗證
ü 使用soapcpp2生成C檔案 : soapcpp2 -c onvif.h -x
-I D:\work\onvif_frame\gsoap_2.8.27\gsoap-2.8\gsoap\import
-ID:\work\onvif_frame\gsoap_2.8.27\gsoap-2.8\gsoap\custom
-ID:\work\onvif_frame\gsoap_2.8.27\gsoap-2.8\gsoap
注意:做伺服器端開發,server和client端的代碼都要生成,因為sever端的代碼要用到client中的函數這樣就不需要自己寫了。
http://blog.csdn.net/hbuxiaofei/article/details/50314759
2.2ONVIF編碼
ü 使用函數soap_wsse_add_UsernameTokenDigest進行使用者名與密碼認證。
int iRet = soap_wsse_add_UsernameTokenDigest(soap,NULL, “使用者名”, “密碼”);
2.3 ONVIF安全認證—openssl
² 在ONVIF_WG-APG-Application_Programmer's_Guide.pdf文檔中第6章描述了onvif加密方式。
² Soap通信的驗證機制是WS_UsernameToken,流加密的方式是HTTPS。
² onvif的使用者驗證是基于WS_UsernameToken,而且密碼是Digest而不是明文。
² WS_UsernameToken加密,就是将使用者名\密碼\Nonce\Created都包含在了header裡面。
将#passwordDigest換成#passwordText的話,密碼就是明文的,當然onvif說了,密碼是Digest
擷取Digest的公式:
Digest = B64ENCODE( SHA1( B64DECODE(Nonce ) + Date + Password ) )
在擷取裝置參數之前,每次都需要作鑒權處理。
2.3.1 鑒權
² 對于設有使用者名和密碼的裝置,在進行一些操作之前需要進行鑒權。
² gsoap用soap_wsse_add_UsernameTokenDigest()函數進行鑒權。
l #include<wsseapi.h> //包含了 soap_wsse_add_UsernameTokenDigest()函數。
l 要使用這個頭檔案還需要添加幾個相關的檔案(具體需要包含的檔案後面詳說),并且需要安裝openssl。
l 函數原型:
int iRet = soap_wsse_add_UsernameTokenDigest(SOAP *soap,
const char* id, //通常設定為NULL
const char* username, //登陸使用者名
const char* password); //登陸密碼
2.3.1 編譯openssl
² 下載下傳ActivePerl:http://www.activestate.com/activeperl/downloads
l 安裝ActivePerl。
l 在cmd裡輸入 perl -v看看是否能出來版本号。若提示找不到,就手動設一下環境變量,把“你的路徑\Perl64\bin;你的路徑\Perl64\site\bin;”添加到PATH裡。
² 下載下傳openssl:http://www.openssl.org/source/
l 解壓。在根目錄下有INSTALL.W32,INSTALL.W64之類的檔案,描述了在各個系統下怎麼樣編譯openssl。參考這些檔案進行如下的操作。
l 打開vs2010的指令提示符輸入:
>cd bin;
>vcvars32.bat
l cd到openssl目錄下:
32位輸入:
> perl Configure VC-WIN32
> ms\do_nasm
> nmake -f ms\ntdll.mak
l 64位輸入:
> perl Configure VC-WIN64A
> ms\do_win64a
> nmake -f ms\ntdll.mak
執行成功後會在out32dll(out32dll64)檔案夾裡生成lib,dll等檔案。
libeay32.lib, libeay32.dll
ssleay.lib, ssleay32.dll
使用上述方法編譯openssl1.1.0f可能不能成功。還需以下步驟:
從http://www.nasm.us/ 下載下傳并安裝Nasm彙編器。
² vs使用openssl
l 添加對openssl的支援,在wsdl2.h生成的onvif.h中添加
#import "wsa.h"
#import "wsse.h"
l 在vs工程裡
添加包含目錄:openssl\inc32
添加導入庫檔案:libeay32.lib ssleay32.lib
添加動态庫檔案:libeay32.dll ssleay32.dll
添加預處理器定義:WITH_DOM WITH_OPENSSL
(不添加預處理器定義會出現“ERR_get_error”: 找不到辨別符、“RAND_pseudo_bytes”: 找不到辨別符)
² 鑒權函數使用
l 将gsoap\plugin目錄下的mecevp, smdevp, threads, wsaapi, wsseapi的.h和.c檔案拷貝到工程目錄下(要用cpp可以直接更改字尾)。
l 在include“wsseapi.h”,就能使用soap_wsse_add_UsernameTokenDigest函數進行鑒權了。
2.3.2 openssl使用
OpenSSL(open Secure Sockets Layer)是一個強大的安全套接字層密碼庫。
l Apache使用它加密HTTPS,OpenSSH使用它加密SSH。
l 但是,你不應該隻将其作為一個庫來使用,它還是一個多用途的、跨平台的密碼工具。
OpenSSL 不僅僅是 SSL。它可以實作消息摘要、檔案的加密和解密、數字證書、數字簽名 和随機數字。
OpenSSL 不隻是 API,它還是一個指令行工具。指令行工具可以完成與 API 同樣的工作, 而且更進一步,可以測試 SSL 伺服器和客戶機。它還讓開發人員對 OpenSSL 的能力有一個 認識。
一些 Linux 的發行版本附帶了 OpenSSL 的二進制版本。
頭檔案ssl.h、bio.h 和 err.h
/* OpenSSL headers */
#include "openssl/bio.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
/* Initializing OpenSSL */
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
2.3.3 openssl建立非安全連接配接
l 不管連接配接是安全的還是不安全的,OpenSSL 都使用了一個名為 BIO 的抽象庫來處理包括檔案和套接字在内的各種類型的通信。
l 在建立連接配接(無論安全與否)之前,要建立一個指向 BIO 對象的指針。這類似于在标準 C 中 為檔案流建立 FILE 指針。
BIO * bio;
bio = BIO_new_connect("hostname:port"); //與伺服器建立連接配接,如www.ibm.com:80
if(bio == NULL)
{
/* Handle the failure */
}
if(BIO_do_connect(bio) <= 0)
{
/* Handle failed connection */
}
l 與伺服器進行通信
不管 BIO 對象是套接字還是檔案,對其進行的讀和寫操作都是通過以下兩個函數來完成的: BIO_read 和 BIO_write 。
BIO_read 将嘗試從伺服器讀取一定數目的位元組; (分阻塞和非阻塞)
BIO_write 會試着将位元組寫入套接字。
l 關閉連接配接
/* To reuse the connection, use this line */
BIO_reset(bio);
/* To free it from memory, use this line */
BIO_free_all(bio);
第三節 認證方式
nonce(随機數)、timestamp(時間戳)、signatrue(簽名)
3.1 Basic認證
1、HTTP規範裡定義的Basic認證——Basic認證及其安全問題
1) Basic認證是一個流程比較簡單的協定,整個過程可以分為以下三個步驟:
a) 用戶端使用GET方法向伺服器請求資源。
b) 伺服器傳回401響應碼和WWW-Authentication:Basic realm=”Family”響應頭要求用戶端進行身份驗證。其中realm聲明了資源所在的域。
例子:
www-Authenticate: Basic realm=”bcad28dde190”
c) 浏覽器接收到以上HTTP響應頭後,彈出登入框要求使用者輸入使用者名和密碼;
使用者送出的使用者名和密碼通過冒号串聯起來并對其進行BASE64編碼後再送出到伺服器;
伺服器對送出上來的BASE64字元串進行驗證,如果驗證通過則傳回200響應碼。
Base64(使用者名:密碼)的結果:
Base64(“admin:123456abc”) = “YWRtaW46MTIzNDU2YWJj”
2) Basic認證雖然簡單、友善,但它隻能作為對非敏感資源的通路認證,因為它并不安全,主要表現在以下幾個方面:
<1> 用戶端送出的使用者名和密碼隻經過簡單的編碼,攻擊者隻要竊聽到該資料包,便可很容易的将其反編碼為原始使用者名和密碼。
<2> 即使用戶端使用了一種比BASE64更複雜的編碼方式使得攻擊者無法對其反編碼,攻擊者也可以使用fiddler等工具将攔截到的HTTP封包重新送出給伺服器,
伺服器隻對編碼的字元串進行驗證,是以驗證同樣能通過。這種攻擊方法稱之為重播攻擊(Replay-Attack)。
以上兩個問題也是各種身份認證協定需要考慮到的安全問題,包括OAuth、Digest認證、NTLM認證等等認證機制都使用了nonce和timestamp來解決這些問題。
3.2 Nonce随機數
2、Nonce、Timestamp——解決Replay-Attack問題(重播攻擊)
(http://www.cnblogs.com/bestzrz/archive/2011/09/03/2164620.html)
1) Nonce是由伺服器生成的一個随機數,在用戶端第一次請求頁面時将其發回用戶端;
www-Authenticate:Digest realm=”bcad288dde190”, nonce=”a8e8e43483844d2a61578781ea5e7d1f”,stale=”FALSE”
2) 用戶端拿到這個Nonce,将其與使用者密碼串聯在一起并進行非可逆加密(MD5、SHA1等等),
3) 用戶端然後将這個加密後的字元串和使用者名、Nonce、加密算法名稱一起發回伺服器。
Authorization : Digest username=”admin”, realm=”bcad288dde190”, nonce=” a8e8e43483844d2a61578781ea5e7d1f”
reponse=”10b789bd1dd846539875df5c09eae836”
4)伺服器使用接收到的使用者名到資料庫搜尋密碼,然後跟用戶端使用同樣的算法對其進行加密;
5)接着伺服器将加密後的字元串與用戶端送出上來的加密字元串進行比較,如果兩個字元串一緻就表示使用者身份有效。
<1> 這樣就解決了使用者密碼明文被竊取的問題,攻擊者就算知道了算法名和nonce也無法解密出密碼。
<2> 每個nonce隻能供一個使用者使用一次,這樣就可以防止攻擊者使用重播攻擊,因為該Http封包已經無效。
<3> 可選的實作方式是把每一次請求的Nonce儲存到資料庫,用戶端再一次送出請求時将請求頭中得Nonce與資料庫中得資料作比較,
如果已存在該Nonce,則證明該請求有可能是惡意的。
<4> 然而這種解決方案也有個問題,很有可能在兩次正常的資源請求中,産生的随機數是一樣的,這樣就造成正常的請求也被當成了攻擊,
随着資料庫中儲存的随機數不斷增多,這個問題就會變得很明顯。是以,還需要加上另外一個參數Timestamp(時間戳)。
<5> Timestamp是根據伺服器目前時間生成的一個字元串,與nonce放在一起,可以表示伺服器在某個時間點生成的随機數。
這樣就算生成的随機數相同,但因為它們生成的時間點不一樣,是以也算有效的随機數。
<6> 問題又來了,随着使用者通路的增加,資料庫中儲存的nonce/timestamp/username資料量會變得非常大。
對于這個問題,可選的解決方案是對資料設定一個“過期時間”,比如說在資料庫中儲存超過一天的資料将會被清除。
<7> 如果是這樣的,攻擊者可以等待一天後,再将攔截到的HTTP封包送出到伺服器,這時候因為nonce/timestamp/username資料已被伺服器清除,
請求将會被認為是有效的。要解決這個問題,就需要給時間戳設定一個逾時時間,
比如說将時間戳與伺服器目前時間比較,如果相差一天則認為該時間戳是無效的
第四節 建立soap對象
struct soap*m_soap;
建立sopa對象
m_soap = soap_new();
m_soap->recv_timeout = 100; //設定逾時
soap_set_namespace(m_soap, namespaces);
設定soap頭
struct SOAP_ENV__Header header;
soap_default_SOAP_ENV__HEADER(m_soap, &header);
釋放soap
soap_destory(m_soap);
soap_end(m_soap);
soap_free(m_soap);
第五節 搜尋裝置
wsdd__ProbeType req;
struct __wsdd__ProbeMatches resp;
發送廣播消息探測
soap_send___wsdd__Probe(soap, “soap.udp://239.255.255.255.250:3702”,NULL, &req);
接收響應消息
soap_recv___wsdd__ProbeMathces(soap, &resp);
第六節 擷取裝置能力集
struct _tds__GetCapabilities tds__GetCapabilities;
struct _tds__GetCapabilitiesResponse tds__GetCapabilitiesResponse;
soap_call__tds___GetCapabilites(soap, “http: //IPAdress:port/onvif/device_service”,NULL,
&tds__GetCapabilities,tds__GetCapabilitiesResponse);
第七節 擷取裝置屬性
_trt__GetProfiles trt__GetProfiles;
_trt__GetProfilesResponse trt__GetProfilesResponse;
soap_call___trt__GetProfiles(soap, “http: //IPAdress:port/onvif/device_service”,NULL,
&trt__GetProfiles, trt__GetProfilesResponse );
獲得實時預覽的Token
第八節 擷取實時預覽RTSP的URL
_trt__GetStreamUri trt__GetStreamUri;
_trt__GetStreamUriResponse trt__GetStreamUriResponse;
soap_call___trt__GetStreamUri(soap, “http://IPAdress:port/onvif/Media”, NULL,
&trt__GetStreamUri, trt__GetStreamUriResponse);
第九節 擷取曆史回放RTSP的URL
獲得回放的Token
_trc__GetRecodings trc__GetRecodings;
_trc__GetRecodingsResponse trc__GetRecodingsResponse;
soap_call___trc__GetRecoding(soap, “http://IPAdress:Port/onvif/recoding_service”,NULL,
&trc__GetRecodings, trc__GetRecodingsResponse);
擷取回放的URI
_trp__GetReplayUri trp__GetReplayUri;
_trp__GetReplayUriResponse trp__GetReplayUriResponse;
soap_call___trp__GetReplayUri(soap, “http://IPAdress:Port/onvif/replay_service”,NULL,
&trp__GetReplayUri, trp__GetReplayUriResponse);
第十節 雲台操控
char* ptzEndPort = “http://192.168.207.65:80/onvif/PTZ”;
_tptz__ContinuousMove request;
_tptz__ContinuousMoveResponse response;
//設定參數
request.ProfileToken = “Profile_1”;
tt__PTZSpeed ptzSpeed;
tt__Vector2D vector2D;
vector2D.x = 1; 取值為[-1, 1],為負數時表示往左,為正數時表示往右
vector2D.y = 0;
vector2D.space = NULL;
ptzSpeed.PanTilt = & vector2D; //Pan上下,Tilt左右
ptzSpeed.Zoom = NULL;
int ret = soap_call___tptz__ContinuousMove(soap,ptzEndPort, NULL, &request, response); 連續移動
停止移動
soap_call___tptz__Stop();
class tt__PTZSpeed : public xsd__anyType
{
public:
tt__Vector2D* PanTitle;
tt__Vector1D* Zoom;
};
class tt__PTZVector : public xsd__anyType
{
public:
tt__Vector2D* PanTitle;
tt__Vector1D* Zoom;
};
class tt__Vector1D : public xsd__anyType
{
public:
float x; // 必須屬性,取值範圍[0, 1]
space; //可選屬性
};
class tt__Vector1D : public xsd__anyType
{
public:
float x; // 必須屬性,取值範圍[0, 1]
float y; // 必須屬性,取值範圍[0, 1]
space; //可選屬性
};
調焦
int soap_call___timg__Move(… , _timg__Move*timg__Move, _timg__MoveResponse& timg_MoveResp);
soap_call___timg__Stop()
第十一節 錄像檢索
_tse__GetRecodingSummary tse_GetRecodingSummary;
_tse__GetRecodingSummaryResponse tse_GetRecodingSummaryResponse;
soap_call___tse__GetRecordingSummary(soap,soap_search_endpoint, NULL,
& tse_GetRecodingSummary, tse_GetRecodingSummaryResponse);
得到DVR中的錄像開始時間和結束時間。
time_t startTime = tse_GetRecodingSummaryResponse.Summary->DataFrom;
time_t endTime = tse_GetRecodingSummaryResponse.Summary->DataUntil;