从ONVIF的官方文档中可以了解到,客户端在UDP协议下,向网段内的组播地址239.255.255.250,端口3702,不断地向四周发送Probe消息探针,而网段内的服务器在接收到Probe这个探测消息后,通过回复ProbeMatch消息让客户端接收,从而让客户端识别到服务器。
所以服务器端就需要创建一个UDP协议的socket,去监听239.255.255.250:3702,接收到客户端的Probe探针后,进行响应,从而让客户端识别到onvif服务器。
1. onvif服务器框架代码
服务器的框架代码在上一篇博客中已经实现,详情可以参考《onvif服务器篇之onvif 服务器框架的搭建》。
2.onvifServerInterface.c
接口函数
在框架代码的基础上,我们需要创建一个
onvifServerInterface.c
文件,去实现一些接口函数,这些接口函数来自于各个wsdl文件对应模块的接口,但是这些接口需要我们自己去实现,这是ONVIF的实现机制,哪怕没有调用到,也要给一个空函数体,这一点在《onvif服务器篇之onvif 服务器框架的搭建》最后一段中有交代。
3.main.c
主函数
main.c实现的功能是在用来建立socket去监听239.255.255.250,端口3702,这里在前面设备发现的机制中已经有提到。
1.onvifServerInterface.c
的实现
#include "wsaapi.h"
#include "soapH.h"
#include "soapStub.h"
#include "wsdd.nsmap"
SOAP_FMAC5 int SOAP_FMAC6 __wsdd__Hello(struct soap* soap, struct wsdd__HelloType *wsdd__Hello)
{
return 0;
}
SOAP_FMAC5 int SOAP_FMAC6 __wsdd__Bye(struct soap* soap, struct wsdd__ByeType *wsdd__Bye)
{
return 0;
}
SOAP_FMAC5 int SOAP_FMAC6 __wsdd__ProbeMatches(struct soap* soap, struct wsdd__ProbeMatchesType *wsdd__ProbeMatches)
{
return 0;
}
SOAP_FMAC5 int SOAP_FMAC6 __wsdd__Resolve(struct soap* soap, struct wsdd__ResolveType *wsdd__Resolve)
{
return 0;
}
SOAP_FMAC5 int SOAP_FMAC6 __wsdd__ResolveMatches(struct soap* soap, struct wsdd__ResolveMatchesType *wsdd__ResolveMatches)
{
return 0;
}
#define IP 192.168.20.1 //服务器IP
#define ONVIF_TCP_PORT 5000 //服务器访问端口(随意设置)
SOAP_FMAC5 int SOAP_FMAC6 __wsdd__Probe(struct soap* soap, struct wsdd__ProbeType *wsdd__Probe)
{
char scopes_message[] =
"onvif://www.onvif.org/type/NetworkVideoTransmitter\r\n"
"onvif://www.onvif.org/Profile/Streaming\r\n"
"onvif://www.onvif.org/Profile/Q/Operational\r\n"
"onvif://www.onvif.org/hardware/HD1080P\r\n"
"onvif://www.onvif.org/name/discover_test\r\n"
"onvif://www.onvif.org/location/city/GuangZhou\r\n"
"onvif://www.onvif.org/location/country/China\r\n";
// response ProbeMatches
struct wsdd__ProbeMatchesType wsdd__ProbeMatches = {0};
struct wsdd__ProbeMatchType *pProbeMatchType = NULL;
struct wsa__Relationship *pWsa__RelatesTo = NULL;
pProbeMatchType = (struct wsdd__ProbeMatchType*)soap_malloc(soap, sizeof(struct wsdd__ProbeMatchType));
soap_default_wsdd__ProbeMatchType(soap, pProbeMatchType);
char str_tmp[256] = {0};
sprintf(str_tmp, "http://%s:%d/onvif/device_service", IP, ONVIF_TCP_PORT);
pProbeMatchType->XAddrs = soap_strdup(soap, str_tmp);
if( wsdd__Probe->Types && strlen(wsdd__Probe->Types) )
pProbeMatchType->Types = soap_strdup(soap, wsdd__Probe->Types);
else
pProbeMatchType->Types = soap_strdup(soap, "dn:NetworkVideoTransmitter tds:Device");
pProbeMatchType->MetadataVersion = 1;
// Build Scopes Message
struct wsdd__ScopesType *pScopes = NULL;
pScopes = (struct wsdd__ScopesType *)soap_malloc(soap, sizeof(struct wsdd__ScopesType));
soap_default_wsdd__ScopesType(soap, pScopes);
pScopes->MatchBy = NULL;
pScopes->__item = soap_strdup(soap, scopes_message);
pProbeMatchType->Scopes = pScopes;
char g_uuid[64];
snprintf(g_uuid, 64, "%s", soap_wsa_rand_uuid(soap));
pProbeMatchType->wsa__EndpointReference.Address = soap_strdup(soap, g_uuid);
wsdd__ProbeMatches.__sizeProbeMatch = 1;
wsdd__ProbeMatches.ProbeMatch = pProbeMatchType;
// Build SOAP Header
pWsa__RelatesTo = (struct wsa__Relationship*)soap_malloc(soap, sizeof(struct wsa__Relationship));
soap_default__wsa__RelatesTo(soap, pWsa__RelatesTo);
pWsa__RelatesTo->__item = soap->header->wsa__MessageID;
soap->header->wsa__RelatesTo = pWsa__RelatesTo;
soap->header->wsa__Action = soap_strdup(soap, "http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches");
soap->header->wsa__To = soap_strdup(soap, "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous");
soap_send___wsdd__ProbeMatches(soap, "http://", NULL, &wsdd__ProbeMatches);
return SOAP_OK;
}
2.main.c
的实现
#include "soapH.h"
#define ONVIF_UDP_IP "239.255.255.250"
#define ONVIF_UDP_PORT 3702
//绑定端口
static SOAP_SOCKET SoapBind(struct soap *pSoap, const char *pIp, bool flag)
{
SOAP_SOCKET sockFD = SOAP_INVALID_SOCKET;
if (flag)
{
sockFD = soap_bind(pSoap, ONVIF_UDP_IP, pSoap->port, 10);
if (soap_valid_socket(sockFD))
{
printf("%s:%s:%d flag = %d, sockFD = %d, pSoap->master = %d\n", __FILE__, __func__, __LINE__, flag, sockFD, pSoap->master);
}
else
{
printf("%s:%s:%d flag = %d\n", __FILE__, __func__, __LINE__, flag);
soap_print_fault(pSoap, stderr);
}
}
else
{
sockFD = soap_bind(pSoap, pIp, pSoap->port, 10);
if (soap_valid_socket(sockFD))
printf("%s:%s:%d flag = %d, sockFD = %d, pSoap->master = %d\n", __FILE__, __func__, __LINE__, flag, sockFD, pSoap->master);
else
{
printf("%s:%s:%d flag = %d\n", __FILE__, __func__, __LINE__, flag);
soap_print_fault(pSoap, stderr);
}
}
return sockFD;
}
//加入设备组,设备发现
static void *OnvifBeDiscovered(void *arg) {
printf("[%s][%d][%s][%s] start \n", __FILE__, __LINE__, __TIME__, __func__);
struct soap UDPserverSoap;
struct ip_mreq mcast;
soap_init1(&UDPserverSoap, SOAP_IO_UDP | SOAP_XML_IGNORENS);
soap_set_namespaces(&UDPserverSoap, namespaces);
int m = soap_bind(&UDPserverSoap, NULL, ONVIF_UDP_PORT, 10);
if(!soap_valid_socket(m))
{
soap_print_fault(&UDPserverSoap, stderr);
exit(1);
}
mcast.imr_multiaddr.s_addr = inet_addr(ONVIF_UDP_IP);
mcast.imr_interface.s_addr = htonl(INADDR_ANY);
//开启route
system("route add -net 224.0.0.0 netmask 224.0.0.0 eth0");
//IP_ADD_MEMBERSHIP用于加入某个多播组,之后就可以向这个多播组发送数据或者从多播组接收数据
if(setsockopt(UDPserverSoap.master, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast)) < 0)
{
printf("setsockopt error! error code = %d,err string = %s\n",errno,strerror(errno));
return 0;
}
int fd = -1;
while(1)
{
fd = soap_accept(&UDPserverSoap);
if (!soap_valid_socket(fd)) {
soap_print_fault(&UDPserverSoap, stderr);
exit(1);
}
if( soap_serve(&UDPserverSoap) != SOAP_OK )
{
soap_print_fault(&UDPserverSoap, stderr);
printf("soap_print_fault\n");
}
soap_destroy(&UDPserverSoap);
soap_end(&UDPserverSoap);
}
//分离运行时的环境
soap_done(&UDPserverSoap);
pthread_exit(0);
}
int main(int argc,char ** argv)
{
pthread_t discover = 0;
pthread_create(&discover, NULL, OnvifBeDiscovered, NULL);
pthread_join(discover, 0);
return 0;
}
3. 编译.c文件,生成可执行文件
gcc *.c -o main
生成执行文件之后,直接运行,然后再打开ONVIF Device Manager工具或者ONVIF Device Test Tool工具搜索就成功了。
(注意:服务器和测试工具需要处于同一网段,跨网段的发现需要通过ONVIF代理去实现,这里没有用到代理)