Onvif协议4: 实战设备搜索

目录

1. 前言

2. WS-Discovery原理

3. 多播

4. 设备搜索

4.1 搜索IPC(方式1)

4.2 搜索IPC(方式2)


1. 前言

要访问一个IPC摄像头,或者说要调用IPC摄像头提供的WEB服务接口,就要先知道其IP地址,这就是「设备发现」的过程,或者叫「设备搜索」的过程。ONVIF规范并没有自己定义服务发现框架,而是复用了已经很成熟的WS-Discovery标准,WS-Discovery 协议使得服务能够被客户端发现。我们先了解下什么是WS-Discovery。

2. WS-Discovery原理

WS-Discovery:全称Web Services Dynamic Discovery。

官方技术规范:http://docs.oasis-open.org/ws-dd/discovery/1.1/os/wsdd-discovery-1.1-spec-os.html

我们传统的Web Services服务调用的模式都是这样的:客户端在设计时就预先知道目标服务的地址(IP地址或者域名),客户端基于这个地址进行服务调用。那如果客户端预先不知道目标服务的地址该怎么办?

WS-Discovery(全称为Web Services Dynamic Discovery)标准就是用于解决该问题的,遵循该标准,客户端预先不知道目标服务地址的情况下,可以动态地探测到可用的目标服务,以便进行服务调用。这个过程就是「设备发现」的过程。

3. 多播

多播”也可以称为“组播”,在网络技术的应用并不是很多,网上视频会议、网上视频点播特别适合采用多播方式。因为如果采用单播方式,逐个节点传输,有多少个目标节点,就会有多少次传送过程,这种方式显然效率极低,是不可取的;如果采用不区分目标、全部发送的广播方式,虽然一次可以传送完数据,但是显然达不到区分特定数据接收对象的目的。采用多播方式,既可以实现一次传送所有目标节点的数据,也可以达到只对特定对象传送数据的目的。   

IP网络的多播一般通过多播IP地址来实现。多播IP地址就是D类IP地址,即224.0.0.0至239.255.255.255之间的IP地址。

4. 设备搜索

搜索IPC有两种搜索方式:

  1. 自己实现socket编程(UDP),通过sendto往多播地址发送探测消息(Probe),再使用recvfrom接收IPC的应答消息(ProbeMatch)。
  2. 根据ONVIF标准的remotediscovery.wsdl文档,使用gSOAP工具快速生成框架代码,直接调用其生成的函数接口来搜索IPC。

从原理上来说,这两种方式归根结底是一样的,都是WS-Discovery协议,方式1是自己造轮子(自己码代码),方式2是利用gSOAP快速生成代码。在项目中肯定是要用方式2,之所以要介绍方式1,是为了让大家对搜索IPC的原理、过程有个更深刻的认识。

4.1 搜索IPC(方式1)

直接上代码,如下所示,这里需要说明几点:

设备发现的多播地址为239.255.255.250,端口3702。

从技术层面来说,通过单播、多播、广播三种方式都能探测到IPC,但多播最具实用性。单播得预先知道IPC的地址(那还搜索啥子嘛),没有实用性。多播是ONVIF规定的方式,能搜多到多播组内的所有IPC。广播能搜索到局域网内的所有IPC,但涉及广播风暴的问题,不推荐。

从实际执行结果来看,探测到的应答信息都是一堆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



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>

4.2 搜索IPC(方式2)

这才是我们项目开发中要用到的方式,我们需要用到ONVIF框架代码,如何使用gSOAP生成ONVIF框架代码在专栏前面的文章已经提到了,此次不再赘述。

probe时,可以真接调用probe,或者分开使用send_probe(),recv_probe().看代码,probe()实际上就是封装了send_probe()和recv_probe()。

直接上代码,附加几点说明:

搜索时必须指定设备类型为「dn:NetworkVideoTransmitter」,否则将搜索不到IPC,该值的来源请参考「ONVIF Profile S Specification」(https://www.onvif.org/profiles/profile-s/),看Types章节说明.

#include "soapStub.h"

#include "soapDiscoveryLookupBindingProxy.h"
#include "soapH.h"





//probe消息仿照 soap_wsdd_Probe 函数编写



int main(int argc, char *argv[])

{

    //soap环境变量

    struct soap *soap;



    //发送消息描述

    struct wsdd__ProbeType req;

    struct wsdd__ProbeType wsdd__Probe;



    struct __wsdd__ProbeMatches resp;



    //描述查找那类的Web消息

    struct wsdd__ScopesType sScope;



    //soap消息头消息

    struct SOAP_ENV__Header  header;



    //获得的设备信息个数

    int count = 0;



    //返回值

    int result = 0;



    //存放uuid 格式(8-4-4-4-12)

    char uuid_string[64];



    printf("%s: %d 000: \n", __FUNCTION__, __LINE__);

    sprintf(uuid_string, "464A4854-4323-5242-1234-110000000123");

    printf("uuid = %s \n", uuid_string);


   DiscoveryLookupBindingProxy soap;
 
    soap->recv_timeout = 5;  //超出5s没数据就推出,超时时间



    //将header设置为soap消息,头属性,暂且认为是soap和header绑定

    soap_default_SOAP_ENV__Header(soap, &header);

    header.wsa__MessageID = uuid_string;

    header.wsa__To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery";

    header.wsa__Action = "http://schemas.xmllocal_soap.org/ws/2005/04/discovery/Probe";

    //设置soap头消息的ID

    soap->header = &header;



    //设置soap消息的请求服务属性

    soap_default_wsdd__ScopesType(soap, &sScope);

    sScope.__item = "onvif://www.onvif.org";

    soap_default_wsdd__ProbeType(soap, &req);

    req.Scopes = &sScope;

    req.Types = "ns1:NetworkVideoTransmitter";



    //调用gSoap接口 向 239.255.255.250:3702 发送udp消息

    result = soap.Probe(&req,resp);


    if(result == -1)

    {

        printf("soap error: %d, %s, %s \n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));

        result = soap->error;

    }

    else

    {

              //读取服务器回应的Probematch消息

                printf("soap_recv___wsdd__Probe: __sizeProbeMatch = %d \n", resp.wsdd__ProbeMatches->__sizeProbeMatch);

                printf("Target EP Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);

                printf("Target Type : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Types);

                printf("Target Service Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);

                printf("Target Metadata Version: %d \n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);

                printf("Target Scope Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item);

                count++;

    }

    soap_end(soap);

    return result;

}

参考:

Onvif协议2:使用wsdl生成onvif代码(wsse和digest鉴权)icon-default.png?t=N7T8https://blog.csdn.net/proing/article/details/135839214

Onvif协议1:gSOAP是什么icon-default.png?t=N7T8https://blog.csdn.net/proing/article/details/135827546

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值