前言
ONVIF规范没有自己定义服务发现框架,而是复用了WS-Discovery标准来实现设备发现,接下来我们先了解一下什么是WS-Discovery。
WS-Discovery简介
WS-Discovery全称Web Services Dynamic Discovery。WS-Discovery可以动态地探测到可用的目标服务,以便进行服务调用,从而解决了传统Web Services调用服务模式要预先知道目标服务地址的问题。
WS-Discovery定义了两种模式:Ad hoc模式和Managed模式。
- Ad hoc模式:客户端以多播(multicast)的形式往多播组(multicast group)发送一个Probe(探测)消息搜寻目标服务,在该探测消息中,包含相应的搜寻条件。如果目标服务满足该条件,则直接将响应ProbeMatch消息(服务自身相关的信息,包括地址)回复给客户端。
- Managed模式:即代理模式。Ad hoc模式有个局限性,只能局限于一个较小的网络。Managed模式就是为了解决这个问题的,在Managed模式下,一个维护所有可用目标服务的中心发现代理(Discovery Proxy)被建立起来,客户端只需要将探测消息发送到该发现代理就可以得到相应的目标服务信息。
生成Guid
以下函数是我自己封装的用于生成guid,也可以使用gSoap的soap_wsa_rand_uuid()
函数(在wsaapi.h中)生成guid。
/**
* @description: 生成guid(windows下叫guid,linux下叫uuid),格式为urn:uuid:8-4-4-4-12,由系统随机产生
*
* @brief createGuid
* @param[in][out] cBuf guid值
* @return bool 返回true表示成功,返回false表示失败
*/
bool OnvifFunc::createGuid(char * cBuf)
{
GUID guid;
if (S_OK == ::CoCreateGuid(&guid))
{
// 如果guid生成成功,则将其转为字符串,保存在cBuf中
_snprintf_s(cBuf, 64, 1024
, "{%08X-%04X-%04x-%02X%02X-%02X%02X%02X%02X%02X%02X}"
, guid.Data1
, guid.Data2
, guid.Data3
, guid.Data4[0], guid.Data4[1]
, guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5]
, guid.Data4[6], guid.Data4[7]
);
return true;
}
return false;
}
设备搜索
/**
* @description: 发现设备
*
* @brief discoveryDevice
* @param[in][out] discoveryRetVec 搜索到的设备信息
* @param[in] IPAddr 本机IP地址
* @return bool 返回true表示成功,其余查看soap错误码
*/
bool OnvifFunc::discoveryDevice(std::vector& discoveryRetVec, std::string IPAddr)
{
char cBuf[64] = { 0 };
int iRet = createGuid(cBuf);
if (iRet)
{
DISCOVERYRET discoveryRet;
// 初始化soap
struct soap soap;
soap_set_mode(&soap, SOAP_C_UTFSTRING);
wsddProxy discoveryDev(&soap);
// 绑定IP地址
struct in_addr if_req;
inet_pton(AF_INET, IPAddr.c_str(), static_cast(&if_req)); // 将点分十进制转换为二进制整数
discoveryDev.soap->ipv4_multicast_if = static_cast(soap_malloc(discoveryDev.soap, sizeof(in_addr)));
memset(discoveryDev.soap->ipv4_multicast_if, 0, sizeof(in_addr));
memcpy(discoveryDev.soap->ipv4_multicast_if, (char *)&if_req, sizeof(if_req));
// 设置超时(超过指定时间没有数据就退出)
discoveryDev.soap->recv_timeout = SOAP_SOCK_TIMEOUT;
discoveryDev.soap->send_timeout = SOAP_SOCK_TIMEOUT;
discoveryDev.soap->connect_timeout = SOAP_SOCK_TIMEOUT;
// 初始化soap描述消息头
discoveryDev.soap_header(cBuf, NULL, NULL, NULL, NULL, WSA_TO, WSA_ACTION, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
// 设置寻找的设备的范围和类型,二者至少设定一个,否则可能收到非ONVIF设备,出现异常
struct wsdd__ScopesType scope; // 用于描述查找哪类的Web服务
soap_default_wsdd__ScopesType(discoveryDev.soap, &scope);
scope.__item = SOAP_ITEM; // 设置寻找设备的范围
struct wsdd__ProbeType probe;
soap_default_wsdd__ProbeType(discoveryDev.soap, &probe);
probe.Scopes = &scope;
probe.Types = SOAP_TYPES; // 设置寻找设备的类型
iRet = discoveryDev.send_Probe(SOAP_MCAST_ADDR, NULL, &probe); // 向组播地址广播Probe消息
if (iRet != SOAP_OK)
{
//清除变量
discoveryDev.destroy();
return false;
}
else
{
// 开始循环接收设备发送过来的消息
while (1)
{
struct __wsdd__ProbeMatches tmp;
// 接收ProbeMatches,成功返回SOAP_OK
iRet = discoveryDev.recv_ProbeMatches(tmp);
if (iRet != SOAP_OK)
{
break;
}
else
{
// 将接收到的数据存入结构体
discoveryRet.deviceXAddrs = tmp.wsdd__ProbeMatches->ProbeMatch->XAddrs;
discoveryRet.ulMetadataVersion = tmp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion;
discoveryRet.Types = tmp.wsdd__ProbeMatches->ProbeMatch->Types;
discoveryRet.Address = tmp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address;
discoveryRet.Item = tmp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item;
discoveryRetVec.push_back(discoveryRet);
}
}
}
//清除变量
discoveryDev.destroy();
return true;
}
return false;
}
特别注意:网上99.99%的中文ONVIF教程在讲解ONVIF设备发现功能时,都没有在代码中体现本机IP地址绑定(包括笔者文末参考中的那篇博客),没有绑定本机IP将会搜索不到设备。
上述代码均为核心代码。
参考
- ONVIF官网
- ONVIF协议网络摄像机(IPC)客户端程序开发(7):设备搜索