相关配置
ONVIF官网:http://www.onvif.org/
gSOAP安装配置:gSOAP安装配置+使用案例参考+参考链接
操作系统:CentOS7
资料参考:
许振坪的ONVIF专栏:传送门
onvif开发之设备发现功能的实现
Linux下onvif服务端之发现设备
还有一个博文找不到网址了,如果网友发现可以私信补充。
代码实战
客户端
1、WS-Discovery的Ad hoc模式,使用多播(不使用gSOAP实现设备发现)
参考文章:ONVIF协议网络摄像机(IPC)客户端程序开发(7):设备搜索
源码如下 search_c.c:
#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;
}
Linux编译: gcc search_c.c
, Windows下编译: gcc search_c.c -lws2_32
Windows下效果和Linux下效果一样。
我Linux是用的虚拟机,所以需要关闭防火墙,不然搜不到 systemctl stop iptables.service
或 service iptables stop
我们也使用 ONVIF Device Test Tool,测一下
2、使用gSOAP生成ONVIF框架,编写客户端
如何生成ONVIF框架参考:ONVIF协议网络摄像机(IPC)客户端程序开发(6):使用gSOAP生成ONVIF框架代码,原文没有生成soapServer.c,我在评论里面写了如何修改,可以参考。
部分文件下载于:GitHub
参考文章:onvif开发之设备发现功能的实现
我做的相关改动都写在评论区了,server编译通不过,但client是可行的。
client.c源码如下:
#include <stdio.h>
#include <stdlib.h>
#include <uuid/uuid.h>
#include "soapH.h"
#include "soapStub.h"
#include "wsdd.nsmap"
#include "wsaapi.h"
#define MULTICAST_ADDRESS "soap.udp://239.255.255.250:3702"
int main()
{
printf("[%s][%d][%s][%s] start \n", __FILE__, __LINE__, __TIME__, __func__);
int result = 0;
wsdd__ProbeType req;
struct __wsdd__ProbeMatches resp;
wsdd__ScopesType sScope;
struct SOAP_ENV__Header header;
struct soap *soap;
soap = soap_new();
if(NULL == soap )
{
printf("sopa new error\r\n");
return -1;
}
soap->recv_timeout = 10;
soap_set_namespaces(soap, namespaces);
soap_default_SOAP_ENV__Header(soap, &header);
uuid_t uuid;
char guid_string[100];
uuid_generate(uuid);
uuid_unparse(uuid, guid_string);
header.wsa__MessageID = guid_string;
header.wsa__To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery";
header.wsa__Action = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe";
soap->header = &header;
soap_default_wsdd__ScopesType(soap, &sScope);
sScope.__item = "";
soap_default_wsdd__ProbeType(soap, &req);
req.Scopes = &sScope;
req.Types = "dn:NetworkVideoTransmitter";
//"dn:NetworkVideoTransmitter";
int i = 0;
result = soap_send___wsdd__Probe(soap, MULTICAST_ADDRESS, NULL, &req);
while(result == SOAP_OK)
{
result = soap_recv___wsdd__ProbeMatches(soap, &resp);
if(result == SOAP_OK)
{
if(soap->error)
{
printf("soap error 1: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
result = soap->error;
}
else
{
printf("guog *********************************************\r\n");
if(soap->header->wsa__MessageID)
{
printf("MessageID : %s\r\n", soap->header->wsa__MessageID);
}
if(soap->header->wsa__RelatesTo && soap->header->wsa__RelatesTo->__item)
{
printf("RelatesTo : %s\r\n", soap->header->wsa__RelatesTo->__item);
}
if(soap->header->wsa__To)
{
printf("To : %s\r\n", soap->header->wsa__To);
}
if(soap->header->wsa__Action)
{
printf("Action : %s\r\n", soap->header->wsa__Action);
}
for(i = 0; i < resp.wsdd__ProbeMatches->__sizeProbeMatch; i++)
{
printf("__sizeProbeMatch : %d\r\n", resp.wsdd__ProbeMatches->__sizeProbeMatch);
printf("wsa__EndpointReference : %p\r\n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference);
printf("Target EP Address : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);
printf("Target Type : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->Types);
printf("Target Service Address : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);
printf("Target Metadata Version : %d\r\n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);
if(resp.wsdd__ProbeMatches->ProbeMatch->Scopes)
{
printf("Target Scopes Address : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item);
}
}
}
}
else if (soap->error)
{
printf("[%d] soap error 2: %d, %s, %s\n", __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
result = soap->error;
}
}
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
printf("[%d] guog discover over !\n", __LINE__);
return result;
}
Linux下编译: gcc -o client client.c stdsoap2.c soapC.c soapClient.c wsaapi.c duration.c -luuid
没有libuuid库,可自行安装,命令仅供参考 sudo apt-get install uuid-dev
效果如图:
服务端
1、不使用gSOAP,虚拟机伪装成ipc
文章参考:Linux下onvif服务端之发现设备
代码如下 ipc_server_c.c:
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <netdb.h>
//#include <errno.h>
//#include <uuid/uuid.h>
#define BUFLEN 4095
int main (int argc, char **argv)
{
char uu_buf[1024]={0};
char *cust_uuid = 0;
struct sockaddr_in groupcast_addr,the_member;
int sockfd;
unsigned char loop;
char recmsg[BUFLEN + 1];
unsigned int socklen, n;
struct ip_mreq mreq;
#if 0
char *msg="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<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:wsdd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:chan=\"http://schemas.microsoft.com/ws/2005/02/duplex\" xmlns:wsa5=\"http://www.w3.org/2005/08/addressing\" xmlns:xmime=\"http://tempuri.org/xmime.xsd\" xmlns:xop=\"http://www.w3.org/2004/08/xop/include\" xmlns:tt=\"http://www.onvif.org/ver10/schema\" xmlns:wsrfbf=\"http://docs.oasis-open.org/wsrf/bf-2\" xmlns:wstop=\"http://docs.oasis-open.org/wsn/t-1\" xmlns:wsrfr=\"http://docs.oasis-open.org/wsrf/r-2\" xmlns:tdn=\"http://www.onvif.org/ver10/network/wsdl\" xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tev=\"http://www.onvif.org/ver10/events/wsdl\" xmlns:wsnt=\"http://docs.oasis-open.org/wsn/b-2\" xmlns:tptz=\"http://www.onvif.org/ver20/ptz/wsdl\" xmlns:trt=\"http://www.onvif.org/ver10/media/wsdl\">\n\
<SOAP-ENV:Header>\n\
<wsa:MessageID>uuid:2419d68a-2dd2-21b2-a205-4A69A95DB56D</wsa:MessageID>\n\
<wsa:RelatesTo>uuid:251984b5-d773-4d2e-a21d-d248cdd2eebf</wsa:RelatesTo>\n\
<wsa:To SOAP-ENV:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>\n\
<wsa:Action SOAP-ENV:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action>\n\
</SOAP-ENV:Header>\n\
<SOAP-ENV:Body>\n\
<wsdd:ProbeMatches>\n\
<wsdd:ProbeMatch>\n\
<wsa:EndpointReference>\n\
<wsa:Address>urn:uuid:2419d68a-2dd2-21b2-a205-4A69A95DB56D</wsa:Address>\n\
<wsa:ReferenceProperties />\n\
<wsa:ReferenceParameters />\n\
<wsa:PortType>ttl</wsa:PortType>\n\
</wsa:EndpointReference>\n\
<wsdd:Types>tds:Device</wsdd:Types>\n\
<wsdd:Scopes>onvif://www.onvif.org/type/NetworkVideoTransmitter\r\nonvif://www.onvif.org/name/IPC_2802222\r\nonvif://www.onvif.org/location/Country/China</wsdd:Scopes>\n\
<wsdd:XAddrs>http://192.168.42.103:5000/onvif/device_service</wsdd:XAddrs>\n\
<wsdd:MetadataVersion>1</wsdd:MetadataVersion>\n\</wsdd:ProbeMatch>\n\
</wsdd:ProbeMatches>\n\
</SOAP-ENV:Body>\n\
</SOAP-ENV:Envelope>\n\n";
#endif
char *aa= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<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:wsdd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:chan=\"http://schemas.microsoft.com/ws/2005/02/duplex\" xmlns:wsa5=\"http://www.w3.org/2005/08/addressing\" xmlns:xmime=\"http://tempuri.org/xmime.xsd\" xmlns:xop=\"http://www.w3.org/2004/08/xop/include\" xmlns:tt=\"http://www.onvif.org/ver10/schema\" xmlns:wsrfbf=\"http://docs.oasis-open.org/wsrf/bf-2\" xmlns:wstop=\"http://docs.oasis-open.org/wsn/t-1\" xmlns:wsrfr=\"http://docs.oasis-open.org/wsrf/r-2\" xmlns:tdn=\"http://www.onvif.org/ver10/network/wsdl\" xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tev=\"http://www.onvif.org/ver10/events/wsdl\" xmlns:wsnt=\"http://docs.oasis-open.org/wsn/b-2\" xmlns:tptz=\"http://www.onvif.org/ver20/ptz/wsdl\" xmlns:trt=\"http://www.onvif.org/ver10/media/wsdl\">\n\
<SOAP-ENV:Header>\n\
<wsa:MessageID>uuid:2419d68a-2dd2-21b2-a205-4A69A95DB56D</wsa:MessageID>\n\
<wsa:RelatesTo>uuid:";
char *bb = "</wsa:RelatesTo>\n\
<wsa:To SOAP-ENV:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>\n\
<wsa:Action SOAP-ENV:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action>\n\
</SOAP-ENV:Header>\n\
<SOAP-ENV:Body>\n\
<wsdd:ProbeMatches>\n\
<wsdd:ProbeMatch>\n\
<wsa:EndpointReference>\n\
<wsa:Address>urn:uuid:2419d68a-2dd2-21b2-a205-4A69A95DB56D</wsa:Address>\n\
<wsa:ReferenceProperties />\n\
<wsa:ReferenceParameters />\n\
<wsa:PortType>ttl</wsa:PortType>\n\
</wsa:EndpointReference>\n\
<wsdd:Types>tds:Device</wsdd:Types>\n\
<wsdd:Scopes>onvif://www.onvif.org/type/NetworkVideoTransmitter\r\nonvif://www.onvif.org/name/IPC_2802222\r\nonvif://www.onvif.org/location/Country/China</wsdd:Scopes>\n\
<wsdd:XAddrs>http://192.168.42.100:5000/onvif/device_service</wsdd:XAddrs>\n\
<wsdd:MetadataVersion>1</wsdd:MetadataVersion>\n\</wsdd:ProbeMatch>\n\
</wsdd:ProbeMatches>\n\
</SOAP-ENV:Body>\n\
</SOAP-ENV:Envelope>\n\n";
/* 创建 socket 用于UDP通讯 */
sockfd = socket (AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
printf ("socket creating err in udptalk\n");
exit (1);
}
/* 设置要加入组播的地址 */
bzero(&mreq, sizeof (struct ip_mreq));
inet_pton(AF_INET,"239.255.255.250",&the_member.sin_addr);
/* 设置组地址 */
bcopy (&the_member.sin_addr.s_addr, &mreq.imr_multiaddr.s_addr, sizeof (struct in_addr));
/* 设置发送组播消息的源主机的地址信息 */
mreq.imr_interface.s_addr = htonl (INADDR_ANY);
/* 把本机加入组播地址,即本机网卡作为组播成员,只有加入组才能收到组播消息 */
//if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP | IP_MULTICAST_LOOP, &mreq,sizeof (struct ip_mreq)) == -1)
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP , &mreq,sizeof (struct ip_mreq)) == -1)
{
perror ("setsockopt");
exit (-1);
}
loop = 0;
if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop,sizeof (loop)) == -1)
{
printf("IP_MULTICAST_LOOP set fail!\n");
}
socklen = sizeof (struct sockaddr_in);
memset (&groupcast_addr, 0, socklen);
groupcast_addr.sin_family = AF_INET;
groupcast_addr.sin_port = htons (3702);
inet_pton(AF_INET, "239.255.255.250", &groupcast_addr.sin_addr);
/* 绑定自己的端口和IP信息到socket上 */
if (bind(sockfd, (struct sockaddr *) &groupcast_addr,sizeof (struct sockaddr_in)) == -1)
{
printf ("Bind error\n");
exit (0);
}
while (1)
{
bzero (recmsg, BUFLEN + 1);
n = recvfrom (sockfd, recmsg, BUFLEN, 0, (struct sockaddr *) &the_member, &socklen);
if (n < 0)
{
printf ("recvfrom err in udptalk!\n");
exit (4);
}
else{
recmsg[n] = 0;
printf ("recv:[%s]\n\n", recmsg);
printf("ip:%s\n",inet_ntoa(the_member.sin_addr));
printf("port:%d\n", ntohs(the_member.sin_port));
}
cust_uuid = strstr(recmsg, "uuid:"); //获取recmsg字符串中 子字符串"uuid:"的位置
if (cust_uuid == 0)
{
printf("uuid: err!\n");
return 0;
}
cust_uuid += 5; //获取接收的uuid的值
strncpy(uu_buf, cust_uuid, 36);
printf("%s\n",uu_buf);
memset(recmsg,0,sizeof(recmsg));
strcpy(recmsg, aa);
strcat(recmsg, uu_buf);
strcat(recmsg, bb);
if (sendto(sockfd, recmsg, strlen (recmsg), 0, (struct sockaddr *) &the_member, sizeof (the_member)) < 0)
{
printf ("sendto error!\n");
exit (3);
}
printf ("send ok\n");
break;
}
}
Linux下编译:gcc ipc_server_c.c -o ipc_server
直接运行
现在我们用 ONVIF Device Test Tool,测试一下
ONVIF Device Test Tool 搜索后,服务端就打印了这些信息
ifconfig查看下虚拟机ip地址
同样可以被我们之前写的 客户端 搜索到
2、使用gSOAP生成ONVIF框架,编写服务端
文件下载于:GitHub
参考文章:onvif服务器篇之onvif 服务器框架的搭建
碰到的问题同样写在评论区,可自行查看。
修改Makefile,GSOAP_ROOT改成自己的soap路径
终端 make
编译。生成 deviceserver
我们直接运行 ./deviceserver
我们同样用 ONVIF Device Test Tool,搜索一下
服务端也有所反应
那么本次的服务端和客户端都测试完毕了。网上资料不多,真的进行的十分困难。
相关链接
ONVIF官网:http://www.onvif.org/
gSOAP安装配置:gSOAP安装配置+使用案例参考+参考链接
许振坪的ONVIF专栏
onvif开发之设备发现功能的实现
Linux下onvif服务端之发现设备
ONVIF协议网络摄像机(IPC)客户端程序开发(7):设备搜索
ONVIF协议网络摄像机(IPC)客户端程序开发(6):使用gSOAP生成ONVIF框架代码
onvif开发之设备发现功能的实现
Linux下onvif服务端之发现设备
GitHub服务端下载
onvif服务器篇之onvif 服务器框架的搭建
ONVIF Device Test Tool