1. 程式人生 > >ONVIF協議網路攝像機(IPC)客戶端程式開發(7):裝置搜尋

ONVIF協議網路攝像機(IPC)客戶端程式開發(7):裝置搜尋

1 專欄導讀

本專欄第一篇文章「專欄開篇」列出了專欄的完整目錄,按目錄順序閱讀,有助於你的理解,專欄前面文章講過的知識點(或程式碼段),後面文章不會贅述。為了節省篇幅,突出重點,在文章中展示的示例程式碼僅僅是關鍵程式碼,你可以在「專欄開篇」中獲取完整程式碼。

如有錯誤,歡迎你的留言糾正!讓我們共同成長!你的「點贊」「打賞」是對我最大的支援和鼓勵!

2 前言

要訪問一個IPC攝像頭,或者說要呼叫IPC攝像頭提供的WEB服務介面,就要先知道其IP地址,這就是「裝置發現」的過程,或者叫「裝置搜尋」的過程。ONVIF規範並沒有自己定義服務發現框架,而是複用了已經很成熟的WS-Discovery標準,WS-Discovery 協議使得服務能夠被客戶端發現。我們先了解下什麼是WS-Discovery。

3 WS-Discovery原理

我們傳統的Web Services服務呼叫的模式都是這樣的:客戶端在設計時就預先知道目標服務的地址(IP地址或者域名),客戶端基於這個地址進行服務呼叫。那如果客戶端預先不知道目標服務的地址該怎麼辦?

WS-Discovery(全稱為Web Services Dynamic Discovery)標準就是用於解決該問題的,遵循該標準,客戶端預先不知道目標服務地址的情況下,可以動態地探測到可用的目標服務,以便進行服務呼叫。這個過程就是「裝置發現」的過程。

WS-Discovery定義了兩種模式:Ad hoc模式和Managed模式。

  • Ad hoc模式:客戶端以多播(multicast)的形式往多播組(multicast group)傳送一個Probe(探測)訊息搜尋目標服務,在該探測訊息中,包含相應的搜尋條件。如果目標服務滿足該條件,則直接將響應ProbeMatch訊息(服務自身相關的資訊,包括地址)回覆給客戶端。

  • Managed模式:即代理模式。Ad hoc模式有個侷限性,只能侷限於一個較小的網路。Managed模式就是為了解決這個問題的,在Managed模式下,一個維護所有可用目標服務的中心發現代理(Discovery Proxy)被建立起來,客戶端只需要將探測訊息傳送到該發現代理就可以得到相應的目標服務資訊。

4 單播、多播(組播)和廣播的區別

WS-Discovery協議用到了多播,那什麼是多播?

TCP/IP有三種傳輸方式:單播(Unicast)、多播(Multicast)和廣播(Broadcast),在IPv6領域還有另一種方式:任播(Anycast)。任播在此不做介紹,以下簡要介紹下單播、多播和廣播的區別:


圖1
  • 單播(Unicast):一對一,雙向通訊,目的地址是對方主機地址。網路上絕大部分的資料都是以單播的形式傳輸的。如收發郵件、瀏覽網頁等。

  • 廣播(Broadcast):一對所有,單向通訊,目的地址是廣播地址,整個網路中所有主機均可以收到(不管你是否需要),如ARP地址解析、GARP資料包等。廣播會被限制在區域網範圍內,禁止廣播資料穿過路由器,防止廣播資料影響大面積的主機。

  • 多播(Multicast):也叫組播,一對多,單向通訊,目的地址是多播地址,主機可以通過IGMP協議請求加入或退出某個多播組(multicast group),資料只會轉發給有需要(已加入組)的主機,不影響其他不需要(未加入組)的主機。如網上視訊會議、網上視訊點播、IPTV等。

網上這篇文章「單播、多播(組播)和廣播的區別」寫的很全面,也區分了他們各自的優缺點,想深入瞭解的可以研究下:

多播地址(Multicast Address)有很多,各個行業都不一樣,IPC攝像頭用的是239.255.255.250(埠3702)。多播地址的範圍和分類可以見官方IANA(網際網路地址分配機構)的說明:IPv4 Multicast Address Space Registry

5 裝置搜尋

回到「裝置發現」這個正軌上,搜尋IPC有兩種搜尋方式:

  1. 自己實現socket程式設計(UDP),通過sendto往多播地址傳送探測訊息(Probe),再使用recvfrom接收IPC的應答訊息(ProbeMatch)。

  2. 根據ONVIF標準的remotediscovery.wsdl文件,使用gSOAP工具快速生成框架程式碼,直接呼叫其生成的函式介面來搜尋IPC。

從原理上來說,這兩種方式歸根結底是一樣的,都是WS-Discovery協議,方式1是自己造輪子(自己碼程式碼),方式2是利用gSOAP快速生成程式碼。在專案中肯定是要用方式2,之所以要介紹方式1,是為了讓大家對搜尋IPC的原理、過程有個更深刻的認識。

5.1 搜尋IPC(方式1)

直接上程式碼,如下所示,這裡需要說明幾點:

  1. 這份程式碼在linux和Windows下都可以使用,其他平臺沒測過。

  2. 裝置發現的多播地址為239.255.255.250,埠3702。

  3. 從技術層面來說,通過單播、多播、廣播三種方式都能探測到IPC,但多播最具實用性。單播得預先知道IPC的地址(那還搜尋啥子嘛),沒有實用性。多播是ONVIF規定的方式,能搜多到多播組內的所有IPC。廣播能搜尋到區域網內的所有IPC,但涉及廣播風暴的問題,不推薦。

  4. const char *probe變數的內容,即探測訊息(Probe)的內容,是ONVIF Device Test Tool 15.06工具搜尋IPC時通過Wireshark抓包工具抓包到的。

  5. 從實際執行結果來看,探測到的應答資訊都是一堆SOAP協議資料包,一堆XML要自己解析,實用性極差,所以這種方式知道下就好,不要在專案使用。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <winsock.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif

/* 從技術層面來說,通過單播、多播、廣播三種方式都能探測到IPC,但多播最具實用性*/
#define COMM_TYPE_UNICAST         1                                             // 單播
#define COMM_TYPE_MULTICAST       2                                             // 多播
#define COMM_TYPE_BROADCAST       3                                             // 廣播
#define COMM_TYPE                 COMM_TYPE_MULTICAST

/* 傳送探測訊息(Probe)的目標地址、埠號 */
#if COMM_TYPE == COMM_TYPE_UNICAST
    #define CAST_ADDR "100.100.100.15"                                          // 單播地址,預先知道的IPC地址
#elif COMM_TYPE == COMM_TYPE_MULTICAST
    #define CAST_ADDR "239.255.255.250"                                         // 多播地址,固定的239.255.255.250
#elif COMM_TYPE == COMM_TYPE_BROADCAST
    #define CAST_ADDR "100.100.100.255"                                         // 廣播地址
#endif

#define CAST_PORT 3702                                                          // 埠號

/* 以下幾個巨集是為了socket程式設計能夠跨平臺,這幾個巨集是從gsoap中拷貝來的 */
#ifndef SOAP_SOCKET
# ifdef WIN32
#  define SOAP_SOCKET SOCKET
#  define soap_closesocket(n) closesocket(n)
# else
#  define SOAP_SOCKET int
#  define soap_closesocket(n) close(n)
# endif
#endif

#if defined(_AIX) || defined(AIX)
# if defined(_AIX43)
#  define SOAP_SOCKLEN_T socklen_t
# else
#  define SOAP_SOCKLEN_T int
# endif
#elif defined(SOCKLEN_T)
# define SOAP_SOCKLEN_T SOCKLEN_T
#elif defined(__socklen_t_defined) || defined(_SOCKLEN_T) || defined(CYGWIN) || defined(FREEBSD) || defined(__FreeBSD__) || defined(OPENBSD) || defined(__QNX__) || defined(QNX) || defined(OS390) || defined(__ANDROID__) || defined(_XOPEN_SOURCE)
# define SOAP_SOCKLEN_T socklen_t
#elif defined(IRIX) || defined(WIN32) || defined(__APPLE__) || defined(SUN_OS) || defined(OPENSERVER) || defined(TRU64) || defined(VXWORKS) || defined(HP_UX)
# define SOAP_SOCKLEN_T int
#elif !defined(SOAP_SOCKLEN_T)
# define SOAP_SOCKLEN_T size_t
#endif

#ifdef WIN32
#define SLEEP(n)    Sleep(1000 * (n))
#else
#define SLEEP(n)    sleep((n))
#endif

/* 探測訊息(Probe),這些內容是ONVIF Device Test Tool 15.06工具搜尋IPC時的Probe訊息,通過Wireshark抓包工具抓包到的 */
const char *probe = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Envelope xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\" xmlns=\"http://www.w3.org/2003/05/soap-envelope\"><Header><wsa:MessageID xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">uuid:fc0bad56-5f5a-47f3-8ae2-c94a4e907d70</wsa:MessageID><wsa:To xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To><wsa:Action xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action></Header><Body><Probe xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\"><Types>dn:NetworkVideoTransmitter</Types><Scopes /></Probe></Body></Envelope>";

int main(int argc, char **argv)
{
    int ret;
    int optval;
    SOAP_SOCKET s;
    SOAP_SOCKLEN_T len;
    char recv_buff[4096] = {0};
    struct sockaddr_in multi_addr;
    struct sockaddr_in client_addr;

#ifdef WIN32
    WSADATA wsaData;
    if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0 ) {                             // 初始化Windows Sockets DLL
        printf("Could not open Windows connection.\n");
        return 0;
    }
    if ( LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2 ) {
        printf("the version of WinSock DLL is not 2.2.\n");
        return 0;
    }
#endif

    s = socket(AF_INET, SOCK_DGRAM, 0);                                         // 建立資料報套接字
    if (s < 0) {
        perror("socket error");
        return -1;
    }

#if COMM_TYPE == COMM_TYPE_BROADCAST
    optval = 1;
    ret = setsockopt(s, SOL_SOCKET, SO_BROADCAST, (const char*)&optval, sizeof(int));
#endif

    multi_addr.sin_family = AF_INET;                                            // 搜尋IPC:使用UDP向指定地址傳送探測訊息(Probe)
    multi_addr.sin_port = htons(CAST_PORT);
    multi_addr.sin_addr.s_addr = inet_addr(CAST_ADDR);
    ret = sendto(s, probe, strlen(probe), 0, (struct sockaddr*)&multi_addr, sizeof(multi_addr));
    if (ret < 0) {
        soap_closesocket(s);
        perror("sendto error");
        return -1;
    }
    printf("Send Probe message to [%s:%d]\n\n", CAST_ADDR, CAST_PORT);
    SLEEP(1);

    for (;;) {                                                                  // 接收IPC的應答訊息(ProbeMatch)
        len = sizeof(client_addr);
        memset(recv_buff, 0, sizeof(recv_buff));
        memset(&client_addr, 0, sizeof(struct sockaddr));
        ret = recvfrom(s, recv_buff, sizeof(recv_buff) - 1, 0, (struct sockaddr*)&client_addr, &len);
        printf("===Recv ProbeMatch from [%s:%d]===\n%s\n\n",  inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), recv_buff);
        SLEEP(1);
    }
    soap_closesocket(s);

    return 0;
}

執行結果如下所示:

Send Probe message to [239.255.255.250:3702]

===Recv ProbeMatch from [100.100.100.15:3702]===
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:d3="http://www.onvif.org/ver10/network/wsdl/RemoteDiscoveryBinding" xmlns:d4="http://www.onvif.org/ver10/network/wsdl/DiscoveryLookupBinding" xmlns:dn="http://www.onvif.org/ver10/network/wsdl"><SOAP-ENV:Header><wsa:MessageID>uuid:283c0c28-4c5c-4318-8c7e-000058f29c9f</wsa:MessageID><wsa:RelatesTo>uuid:fc0bad56-5f5a-47f3-8ae2-c94a4e907d70</wsa:RelatesTo><wsa:To SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To><wsa:Action SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><d:ProbeMatches><d:ProbeMatch><wsa:EndpointReference><wsa:Address>urn:uuid:00b90d02-7408-8301-ac36-00b90d027408</wsa:Address></wsa:EndpointReference><d:Types>dn:NetworkVideoTransmitter</d:Types><d:Scopes>onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/type/audio_encoder onvif://www.onvif.org/type/ptz onvif://www.onvif.org/hardware/HW0100302 onvif://www.onvif.org/location/country/china onvif://www.onvif.org/name/hd onvif://www.onvif.org/Profile/Streaming </d:Scopes><d:XAddrs>http://100.100.100.15:2000/onvif/device_service </d:XAddrs><d:MetadataVersion>32152654</d:MetadataVersion></d:ProbeMatch></d:ProbeMatches></SOAP-ENV:Body></SOAP-ENV:Envelope>


===Recv ProbeMatch from [100.100.100.119:3702]===
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa5="http://www.w3.org/2005/08/addressing" xmlns:xop="http://www.w3.org/2004/08/xop/include" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:tt="http://www.onvif.org/ver10/schema" xmlns:ns1="http://www.w3.org/2005/05/xmlmime" xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" xmlns:ns7="http://docs.oasis-open.org/wsrf/r-2" xmlns:ns2="http://docs.oasis-open.org/wsrf/bf-2" xmlns:dndl="http://www.onvif.org/ver10/network/wsdl/DiscoveryLookupBinding" xmlns:dnrd="http://www.onvif.org/ver10/network/wsdl/RemoteDiscoveryBinding" xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:dn="http://www.onvif.org/ver10/network/wsdl" xmlns:ns10="http://www.onvif.org/ver10/replay/wsdl" xmlns:ns11="http://www.onvif.org/ver10/search/wsdl" xmlns:ns13="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding" xmlns:ns14="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding" xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" xmlns:ns15="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding" xmlns:ns16="http://www.onvif.org/ver10/events/wsdl/EventBinding" xmlns:tev="http://www.onvif.org/ver10/events/wsdl" xmlns:ns17="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding" xmlns:ns18="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding" xmlns:ns19="http://www.onvif.org/ver10/events/wsdl/NotificationConsumerBinding" xmlns:ns20="http://www.onvif.org/ver10/events/wsdl/PullPointBinding" xmlns:ns21="http://www.onvif.org/ver10/events/wsdl/CreatePullPointBinding" xmlns:ns22="http://www.onvif.org/ver10/events/wsdl/PausableSubscriptionManagerBinding" xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" xmlns:ns3="http://www.onvif.org/ver10/analyticsdevice/wsdl" xmlns:ns4="http://www.onvif.org/ver10/deviceIO/wsdl" xmlns:ns5="http://www.onvif.org/ver10/display/wsdl" xmlns:ns8="http://www.onvif.org/ver10/receiver/wsdl" xmlns:ns9="http://www.onvif.org/ver10/recording/wsdl" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:ter="http://www.onvif.org/ver10/error" xmlns:tns1="http://www.onvif.org/ver10/topics" xmlns:tnsn="http://www.eventextension.com/2011/event/topics"><SOAP-ENV:Header><wsa:MessageID>urn:uuid:0b4bede6-5566-7788-99aa-00121312da25</wsa:MessageID><wsa:RelatesTo>uuid:fc0bad56-5f5a-47f3-8ae2-c94a4e907d70</wsa:RelatesTo><wsa:To SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To><wsa:Action SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><d:ProbeMatches><d:ProbeMatch><wsa:EndpointReference><wsa:Address>urn:uuid:0b4bede6-5566-7788-99aa-00121312da25</wsa:Address></wsa:EndpointReference><d:Types>dn:NetworkVideoTransmitter</d:Types><d:Scopes>onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/type/audio_encoder onvif://www.onvif.org/hardware/IPC-model onvif://www.onvif.org/location/country/china onvif://www.onvif.org/name/NVT </d:Scopes><d:XAddrs>http://100.100.100.119:8899/onvif/device_service</d:XAddrs><d:MetadataVersion>1</d:MetadataVersion></d:ProbeMatch></d:ProbeMatches></SOAP-ENV:Body></SOAP-ENV:Envelope>

5.2 搜尋IPC(方式2)

這才是我們專案開發中要用到的方式,我們需要用到ONVIF框架程式碼,如何使用gSOAP生成ONVIF框架程式碼在專欄前面的文章已經提到了,此次不再贅述。

直接上程式碼,附加幾點說明:

  1. 標頭檔案onvif_dump.h是我自己封裝的程式碼,僅僅用於列印IPC應答的資料結構體資訊,具體程式碼就不列出來了。

  2. 搜尋時必須指定裝置型別為「dn:NetworkVideoTransmitter」,否則將搜尋不到IPC,該值的來源請參考「ONVIF Profile S Specification」(https://www.onvif.org/profiles/profile-s/),看Types章節說明,如下所示(摘自ONVIF Profile S Specification Version 1.1.1版本):

    9.1 Types
    Section “Discovery definitions” of the ONVIF Core Specification defines a generic tds:Device for
    the

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "soapH.h"
#include "wsaapi.h"
#include "onvif_dump.h"

#define SOAP_ASSERT     assert
#define SOAP_DBGLOG     printf
#define SOAP_DBGERR     printf

#define SOAP_TO         "urn:schemas-xmlsoap-org:ws:2005:04:discovery"
#define SOAP_ACTION     "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"

#define SOAP_MCAST_ADDR "soap.udp://239.255.255.250:3702"                       // onvif規定的組播地址

#define SOAP_ITEM       ""                                                      // 尋找的裝置範圍
#define SOAP_TYPES      "dn:NetworkVideoTransmitter"                            // 尋找的裝置型別

#define SOAP_SOCK_TIMEOUT    (10)                                               // socket超時時間(單秒秒)

void soap_perror(struct soap *soap, const char *str)
{
    if (NULL == str) {
        SOAP_DBGERR("[soap] error: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
    } else {
        SOAP_DBGERR("[soap] %s error: %d, %s, %s\n", str, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
    }
    return;
}

void* ONVIF_soap_malloc(struct soap *soap, unsigned int n)
{
    void *p = NULL;

    if (n > 0) {
        p = soap_malloc(soap, n);
        SOAP_ASSERT(NULL != p);
        memset(p, 0x00 ,n);
    }
    return p;
}

struct soap *ONVIF_soap_new(int timeout)
{
    struct soap *soap = NULL;                                                   // soap環境變數

    SOAP_ASSERT(NULL != (soap = soap_new()));

    soap_set_namespaces(soap, namespaces);                                      // 設定soap的namespaces
    soap->recv_timeout    = timeout;                                            // 設定超時(超過指定時間沒有資料就退出)
    soap->send_timeout    = timeout;
    soap->connect_timeout = timeout;

#if defined(__linux__) || defined(__linux)                                      // 參考https://www.genivia.com/dev.html#client-c的修改:
    soap->socket_flags = MSG_NOSIGNAL;                                          // To prevent connection reset errors
#endif

    soap_set_mode(soap, SOAP_C_UTFSTRING);                                      // 設定為UTF-8編碼,否則疊加中文OSD會亂碼

    return soap;
}

void ONVIF_soap_delete(struct soap *soap)
{
    soap_destroy(soap);                                                         // remove deserialized class instances (C++ only)
    soap_end(soap);                                                             // Clean up deserialized data (except class instances) and temporary data
    soap_done(soap);                                                            // Reset, close communications, and remove callbacks
    soap_free(soap);                                                            // Reset and deallocate the context created with soap_new or soap_copy
}

/************************************************************************
**函式:ONVIF_init_header
**功能:初始化soap描述訊息頭
**引數:
        [in] soap - soap環境變數
**返回:無
**備註:
    1). 在本函式內部通過ONVIF_soap_malloc分配的記憶體,將在ONVIF_soap_delete中被釋放
************************************************************************/
void ONVIF_init_header(struct soap *soap)
{
    struct SOAP_ENV__Header *header = NULL;

    SOAP_ASSERT(NULL != soap);

    header = (struct SOAP_ENV__Header *)ONVIF_soap_malloc(soap, sizeof(struct SOAP_ENV__Header));
    soap_default_SOAP_ENV__Header(soap, header);
    header->wsa__MessageID = (char*)soap_wsa_rand_uuid(soap);
    header->wsa__To        = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_TO) + 1);
    header->wsa__Action    = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_ACTION) + 1);
    strcpy(header->wsa__To, SOAP_TO);
    strcpy(header->wsa__Action, SOAP_ACTION);
    soap->header = header;

    return;
}

/************************************************************************
**函式:ONVIF_init_ProbeType
**功能:初始化探測裝置的範圍和型別
**引數:
        [in]  soap  - soap環境變數
        [out] probe - 填充要探測的裝置範圍和型別
**返回:
        0表明探測到,非0表明未探測到
**備註:
    1). 在本函式內部通過ONVIF_soap_malloc分配的記憶體,將在ONVIF_soap_delete中被釋放
************************************************************************/
void ONVIF_init_ProbeType(struct soap *soap, struct wsdd__ProbeType *probe)
{
    struct wsdd__ScopesType *scope = NULL;                                      // 用於描述查詢哪類的Web服務

    SOAP_ASSERT(NULL != soap);
    SOAP_ASSERT(NULL != probe);

    scope = (struct wsdd__ScopesType *)ONVIF_soap_malloc(soap, sizeof(struct wsdd__ScopesType));
    soap_default_wsdd__ScopesType(soap, scope);                                 // 設定尋找裝置的範圍
    scope->__item = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_ITEM) + 1);
    strcpy(scope->__item, SOAP_ITEM);

    memset(probe, 0x00, sizeof(struct wsdd__ProbeType));
    soap_default_wsdd__ProbeType(soap, probe);
    probe->Scopes = scope;
    probe->Types  = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_TYPES) + 1);     // 設定尋找裝置的型別
    strcpy(probe->Types, SOAP_TYPES);

    return;
}

void ONVIF_DetectDevice(void (*cb)(char *DeviceXAddr))
{
    int i;
    int result = 0;
    unsigned int count = 0;                                                     // 搜尋到的裝置個數
    struct soap *soap = NULL;                                                   // soap環境變數
    struct wsdd__ProbeType      req;                                            // 用於傳送Probe訊息
    struct __wsdd__ProbeMatches rep;                                            // 用於接收Probe應答
    struct wsdd__ProbeMatchType *probeMatch;

    SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));

    ONVIF_init_header(soap);                                                    // 設定訊息頭描述
    ONVIF_init_ProbeType(soap, &req);                                           // 設定尋找的裝置的範圍和型別
    result = soap_send___wsdd__Probe(soap, SOAP_MCAST_ADDR, NULL, &req);        // 向組播地址廣播Probe訊息
    while (SOAP_OK == result)                                                   // 開始迴圈接收裝置傳送過來的訊息
    {
        memset(&rep, 0x00, sizeof(rep));
        result = soap_recv___wsdd__ProbeMatches(soap, &rep);
        if (SOAP_OK == result) {
            if (soap->error) {
                soap_perror(soap, "ProbeMatches");
            } else {                                                            // 成功接收到裝置的應答訊息
                dump__wsdd__ProbeMatches(&rep);

                if (NULL != rep.wsdd__ProbeMatches) {
                    count += rep.wsdd__ProbeMatches->__sizeProbeMatch;
                    for(i = 0; i < rep.wsdd__ProbeMatches->__sizeProbeMatch; i++) {
                        probeMatch = rep.wsdd__ProbeMatches->ProbeMatch + i;
                        if (NULL != cb) {
                            cb(probeMatch->XAddrs);                             // 使用裝置服務地址執行函式回撥
                        }
                    }
                }
            }
        } else if (soap->error) {
            break;
        }
    }

    SOAP_DBGLOG("\ndetect end! It has detected %d devices!\n", count);

    if (NULL != soap) {
        ONVIF_soap_delete(soap);
    }

    return ;
}

int main(int argc, char **argv)
{
    ONVIF_DetectDevice(NULL);

    return 0;
}

執行結果如下(搜尋到兩個IPC),這裡最重要的一個輸出就是IPC攝像頭的「裝置服務地址」,即XAddrs欄位,後續呼叫其他ONVIF介面,都需要用到「裝置服務地址」。

================= + dump__wsdd__ProbeMatches + >>>
wsdd__ProbeMatches: (0x8ace650)
   |- __sizeProbeMatch: 1
   |- ProbeMatch: (0x8ace654)
      |- 0
         |- wsa__EndpointReference: (0x8acd568)
            |- Address: urn:uuid:00b974cb-7c65-8301-ac36-00b974cb7c65
            |- ReferenceProperties: (null)
            |- ReferenceParameters: (null)
            |- PortType: (null)
            |- ServiceName: (null)
            |- __size: 0
            |- __any: (null)
            |- __anyAttribute: 
         |- Types: tdn:NetworkVideoTransmitter
         |- Scopes: (0x8ace770)
            |- __item: onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/type/audio_encoder onvif://www.onvif.org/type/ptz onvif://www.onvif.org/hardware/HW0100302 onvif://www.onvif.org/location/country/china  onvif://www.onvif.org/name/hd  onvif://www.onvif.org/Profile/Streaming  
            |- MatchBy: (null)
         |- XAddrs: http://100.100.100.24:2000/onvif/device_service 
         |- MetadataVersion: 32152654
================= - dump__wsdd__ProbeMatches - <<<

================= + dump__wsdd__ProbeMatches + >>>
wsdd__ProbeMatches: (0x8ace668)
   |- __sizeProbeMatch: 1
   |- ProbeMatch: (0x8ace66c)
      |- 0
         |- wsa__EndpointReference: (0x8acd750)
            |- Address: urn:uuid:00b90d02-7408-8301-ac36-00b90d027408
            |- ReferenceProperties: (null)
            |- ReferenceParameters: (null)
            |- PortType: (null)
            |- ServiceName: (null)
            |- __size: 0
            |- __any: (null)
            |- __anyAttribute: 
         |- Types: tdn:NetworkVideoTransmitter
         |- Scopes: (0x8aceba0)
            |- __item: onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/type/audio_encoder onvif://www.onvif.org/type/ptz onvif://www.onvif.org/hardware/HW0100302 onvif://www.onvif.org/location/country/china onvif://www.onvif.org/name/hd onvif://www.onvif.org/Profile/Streaming 
            |- MatchBy: (null)
         |- XAddrs: http://100.100.100.15:2000/onvif/device_service 
         |- MetadataVersion: 32152654
================= - dump__wsdd__ProbeMatches - <<<
detect end! It has detected 2 devices!