zoukankan      html  css  js  c++  java
  • 设备发现协议

      网络环境下设备发现是一种比较常见的应用,比如查找打印机与WiFi。那么我们应该如何通过编程实现对网络中的特定设备进行查找呢?

      常用的方式有:IP广播与多播,以及基于这两种方式所实现的第三方协议,较著名的有Onvif协议。

    1局域网广播

    1.1 定义

      广播是一种一对所有的通信模式。有线电视网就是典型的广播型网络,我们的电视机实际上是接受到所有频道的信号,但只将一个频道的信号还原成画面。

      广播不用进行网络路径选择,不能穿越路由器。这是为了防止广播数据影响大面积的主机,引起广播灾难。

    1.2 优缺点

    1.2.1 优点

    • 网络设备简单,维护简单,布网成本低廉。
    • 由于服务器不用向每个客户机单独发送数据,所以服务器流量负载极低。

    1.2.2 缺点

    • 无法针对每个客户的要求和时间及时提供个性化服务。
    • 网络允许服务器提供数据的带宽有限,客户端的最大带宽=服务总带宽。例如有线电视的客户端的线路支持100个频道(如果采用数字压缩技术,理论上可以提供 500个频道),即使服务商有更大的财力配置更多的发送设备、改成光纤主干,也无法超过此极限。
    • 不能在广域网上传播,这是为了防止广播风暴。

    1.3 广播地址

      每一个网段都有一个广播地址,其格式为 xxx.xxx.xxx.255 的形式。计算方式如下:

                        网络地址 = IP地址 & 子网掩码

                        广播地址 = 网络地址 | (~子网掩码)

      代码:

    /*
     * 通过ip地址与子网掩码计算广播地址
     * 算法流程:1. ip地址与子网掩码获得网络地址;
     *         2. 网络地址主机位全置1后得到广播地址
    * @param[out] bc_addr 广播地址 * @param[in] ip 主机IPv4地址 * @param[in] mask 子网掩码 * @param[in] len IPv4地址长度(16) * @return 返回广播地址
    */ char* cal_bc_addr(char* bc_addr, const char* ip, const char* mask) { uint8_t bc[4] = {0}; int ret = 0; printf("ip: %s ", ip); printf("mask: %s ", mask); ip[3] = ''; ip[7] = ''; ip[11] = ''; mask[3] = ''; mask[7] = ''; mask[11] = ''; bc[0] = (uint8_t)(atoi(ip) & atoi(mask)) | ~(uint8_t)atoi(mask); bc[1] = (uint8_t)(atoi(ip + 4) & atoi(mask + 4)) | ~(uint8_t)atoi(mask + 4); bc[2] = (uint8_t)(atoi(ip + 8) & atoi(mask + 8)) | ~(uint8_t)atoi(mask + 8); bc[3] = (uint8_t)(atoi(ip + 12) & atoi(mask + 12)) | ~(uint8_t)atoi(mask + 12); ret = sprintf(bc_addr, "%d.%d.%d.%d", bc[0], bc[1], bc[2], bc[3]); bc_addr[ret] = ''; return bc_addr; }
    /**
     * 通过socket获取广播地址(仅限Linux)
     * @param[out] bc_addr 广播地址
     * @param[in] ifname 网口,如:"eth0"
     * @return 返回广播地址
     */
    char* get_bc_addr(char* bc_addr, const char* ifname)
    {
      int sock = socket(AF_INET,SOCK_DGRAM,0);
      if(sock < 0){
        perror("sock init error");
        goto on_return;
      }
      struct ifreq ifr;
      strncpy(ifr.ifr_name, ifname, strlen(ifname));
      if(ioctl(sock, SIOCGIFBRDADDR, &ifr) == -1){ // 获取广播地址
          perror("ioctl error");
          goto on_return;
      }
      memcpy(&bc_addr, (char *)&ifr->ifr_broadaddr, sizeof(struct sockaddr_in));
    on_return:
      if(sock > 0) close(sock);   
    return bc_addr; }

    1.4 socket编程

    1.4.1 IP选项

      广播是基于UDP的,使用时需要设置其IP选项进行设置,详见下表:

    socket编程广播选项设置
    setsockopt()选项 含义
    SO_BROADCAST
    广播

    1.4.2 socket多播程序设计流程

    • 创建UDP套接字;
    • 获取广播地址,设置其端口号;
    • 设置IP选项:SO_BROADCAST;
    • 接收/发送广播消息。

    1.4.3 代码

    服务端:

    #include<iostream>
    #include<stdio.h>
    #include<sys/socket.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<netdb.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<string.h>
    using namespace std;
    int main()
    {
        setvbuf(stdout,NULL,_IONBF,0);
        fflush(stdout);
        int sock=-1;
        if((sock=socket(AF_INET,SOCK_DGRAM,0))==-1)
        {
            cout<<"sock error"<<endl;
            return -1;
        }
        const int opt=-1;
        int nb=0;
        nb=setsockopt(sock,SOL_SOCKET,SO_BROADCAST,(char*)&opt,sizeof(opt));//设置套接字类型
        if(nb==-1)
        {
            cout<<"set socket error...
    "<<endl;
            return -1;
        }
        struct sockaddr_in addrto;
        bzero(&addrto,sizeof(struct sockaddr_in));
        addrto.sin_family=AF_INET;
        addrto.sin_addr.s_addr=htonl(INADDR_BROADCAST);//套接字地址为广播地址
        addrto.sin_port=htons(6000);//套接字广播端口号为6000
        int nlen=sizeof(addrto);
        while(1)
        {
            sleep(1);
            char msg[]={"the message broadcast"};
            int ret=sendto(sock,msg,strlen(msg),0,(sockaddr*)&addrto,nlen);//向广播地址发布消息
            if(ret<0)
            {
                cout<<"send error...
    "<<endl;
                return -1;
            }
            else 
            {
                printf("ok
    ");
            }
        }
        return 0;
    }

    客户端:

    #include<iostream>
    #include<stdio.h>
    #include<sys/socket.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<netdb.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<string.h>
    
    
    using namespace std;
    int main()
    {
      setvbuf(stdout,NULL,_IONBF,0);
      fflush(stdout);
      struct sockaddr_in addrto;
      bzero(&addrto,sizeof(struct sockaddr_in));
      addrto.sin_family=AF_INET;
      addrto.sin_addr.s_addr=htonl(INADDR_ANY);
      addrto.sin_port=htons(6000);
      socklen_t len=sizeof(addrto);
      int sock=-1;
      if((sock=socket(AF_INET,SOCK_DGRAM,0))==-1){
        cout<<"socket error..."<<endl;
        return -1;
      }
      const int opt=-1;
      int nb=0;
      nb=setsockopt(sock,SOL_SOCKET,SO_BROADCAST,(char*)&opt,sizeof(opt));
      if(nb==-1){
        cout<<"set socket errror..."<<endl;
        return -1;
      }
      if(bind(sock,(struct sockaddr*)&(addrto),len)==-1){
        cout<<"bind error..."<<endl;
        return -1;
      }
      char msg[100]={0};
      while(1)
      {
        int ret=recvfrom(sock,msg,100,0,(struct sockaddr*)&addrto,&len);
        if(ret<=0){
          cout<<"read error..."<<endl;
        }else{
          printf("%s	",msg);
        }
        sleep(1);
      }
      return 0;
    }

    2 多播

    2.1 定义

      多播又称为组播,是一种一对多的通信方式。多播能使一个或多个多播源只把数据包发送给特定的多播组,而只有加入该多播组的主机才能接收到数据包。它是节省网络带宽的有效方法之一。在网络音频/视频广播的应用中,当需要将一个节点的信号传送到多个节点时,无论是采用重复点对点通信方式,还是采用广播方式,都会严重浪费网络带宽,只有多播才是最好的选择。

      多播可在广域网传播。由于多播是以组的形式参与的,因此不必担心未加入多播组的主机接收到消息,这一特性使它允许在广域网上传播。

    2.2 多播地址

      在IPv4中,多播地址是一个D类IP地址,其范围从224.0.0.0~239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限(受限)多播地址三类:

      局部多播(224.0.0.0~224.0.0.255):这是为路由协议和其它用途保留的地址,路由器并不转发属于此范围的IP包。

      预留多播(224.0.1.0~238.255.255.255):可用于全球范围(如Internet)或网络协议。

      受限多播(239.0.0.0~239.255.255.255):可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。

    2.3 socket编程 

    2.3.1 IP选项设置

      多播的程序设计使用setsockopt()函数和getsockopt()函数来实现,组播的选项是IP层的,其选项值和含义见下表:

    socket编程多播IP选项设置
    setsockopt()/getsockopt()选项 含义
    IP_MULTICAST_TTL 设置多播组跨网段数
    IP_ADD_MEMBERSHIP 加入多播组
    IP_DROP_MEMBERSHIP 离开多播组
    IP_MULTICAST_IF 获取默认或设置接口
    IP_MULTICAST_LOOP 设置数据回传

    2.3.2 socket多播程序设计流程

    • 创建UPD套接字;
    • 设置IP选项:TTL、MEMBERSHIP、IF、LOOP;
    • 加入多播组;
    • 发送/接收数据;
    • 离开多播组。

    2.3.3 代码

    服务端:

    #include<iostream>
    #include<stdio.h>
    #include<sys/socket.h>
    #include<netdb.h>
    #include<sys/types.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<string.h>
    #define MCAST_PORT 8888
    #define MCAST_ADDR "224.0.0.88"  // 多播地址
    #define MCAST_DATA "BROADCAST TEST DATA"  // 多播内容
    #define MCAST_INTERVAL 5  //多播时间间隔
    using namespace std;
    
    int main()
    {
      int sock;
      struct sockaddr_in mcast_addr;
      sock=socket(AF_INET,SOCK_DGRAM,0);
      if(sock==-1){
        cout<<"socket error"<<endl;
        return -1;
      }
      memset(&mcast_addr,0,sizeof(mcast_addr));
      mcast_addr.sin_family=AF_INET;
      mcast_addr.sin_addr.s_addr=inet_addr(MCAST_ADDR);
      mcast_addr.sin_port=htons(MCAST_PORT);
      while(1)
      {       //向局部多播地址发送多播内容
        int n=sendto(sock,MCAST_DATA,sizeof(MCAST_DATA),0,(struct sockaddr*)&mcast_addr,sizeof(mcast_addr));
        if(n<0){
          cout<<"send error"<<endl;
          return -2;
        }else{
          cout<<"send message is going ...."<<endl;
        }
        sleep(MCAST_INTERVAL);
      }
      return 0;
    }

    客户端:

    #include<iostream>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<sys/types.h>
    #include<unistd.h>
    #include<sys/socket.h>
    #include<netdb.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #define MCAST_PORT 8888
    #define MCAST_ADDR "224.0.0.88" /*一个局部连接多播地址,路由器不进行转发*/
    #define MCAST_INTERVAL 5  //发送时间间隔
    #define BUFF_SIZE 256   //接收缓冲区大小
    using namespace std;
    int main()
    {
      int sock;
      struct sockaddr_in local_addr;
      int err=-1;
      sock=socket(AF_INET,SOCK_DGRAM,0);
      if(sock==-1){
         cout<<"sock error"<<endl;
          return -1;
       }
       /*初始化地址*/
       local_addr.sin_family=AF_INET;
       local_addr.sin_addr.s_addr=htonl(INADDR_ANY);
       local_addr.sin_port=htons(MCAST_PORT);
       /*绑定socket*/
       err=bind(sock,(struct sockaddr*)&local_addr,sizeof(local_addr));
       if(err<0){
         cout<<"bind error"<<endl;
         return -2;
       }
       /*设置回环许可*/
       int loop=1;
       err=setsockopt(sock,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop));
       if(err<0){
         cout<<"set sock error"<<endl;
         return -3;
       }
       struct ip_mreq mreq;/*加入广播组*/
       mreq.imr_multiaddr.s_addr=inet_addr(MCAST_ADDR);//广播地址
       mreq.imr_interface.s_addr=htonl(INADDR_ANY); //网络接口为默认
       /*将本机加入多播组*/
       err=setsockopt(sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
       if(err<0){
         cout<<"set sock error"<<endl;
           return -4;
       }
            
      
    int times=0;   socklen_t addr_len=0;   char buff[BUFF_SIZE];   int n=0;   /*循环接受广播组的消息,5次后退出*/   for(times=0;;times++)   {     addr_len=sizeof(local_addr);     memset(buff,0,BUFF_SIZE);     n=recvfrom(sock,buff,BUFF_SIZE,0,(struct sockaddr*)&local_addr,&addr_len);     if(n==-1){       cout<<"recv error"<<endl;       return -5;     }     /*打印信息*/     printf("RECV %dst message from server : %s ",times,buff);     sleep(MCAST_INTERVAL);   }
    /*退出多播组*/
      err=setsockopt(sock,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(mreq));   close(sock);   return 0; }

     3 Onvif协议

      Onvif协议是Open Network Video Interface Forum(开放型网络视频接口论坛)的英文缩写。

      ONVIF标准将为网络视频设备之间的信息交换定义通用协议,包括装置搜寻、实时视频、音频、元数据和控制信息等。网络视频产品由此所能提供的多种可能性,使终端用户,集成商,顾问和生产厂商能够轻松地从中获益,并获得高性价比、更灵活的解决方案、市场扩张的机会以及更低的风险。

    视频设备发现代码示例:

    #include "wsdd.h"
    #include <stdio.h>
    
    static struct soap* ONVIF_Initsoap(struct SOAP_ENV__Header *header, const char *was_To, const char *was_Action, int timeout)
    {
        struct soap *soap = NULL; 
        unsigned char macaddr[6];
        char _HwId[1024];
        unsigned int Flagrand;
        soap = soap_new();
        if(soap == NULL)
        {
            printf("[%d]soap = NULL
    ", __LINE__);
            return NULL;
        }
        
        soap_set_namespaces( soap, namespaces);
    
        if (timeout > 0)
        {
            soap->recv_timeout = timeout;
            soap->send_timeout = timeout;
            soap->connect_timeout = timeout;
        }
        else
        {
            //Maximum wait time: 20S 
            soap->recv_timeout    = 20;
            soap->send_timeout    = 20;
            soap->connect_timeout = 20;
        }
        soap_default_SOAP_ENV__Header(soap, header);
    
        // Create SessionID randomly
        srand((int)time(0));
        Flagrand = rand()%9000 + 8888; 
        macaddr[0] = 0x1; macaddr[1] = 0x2; macaddr[2] = 0x3; macaddr[3] = 0x4; macaddr[4] = 0x5; macaddr[5] = 0x6;
        sprintf(_HwId,"urn:uuid:%ud68a-1dd2-11b2-a105-%02X%02X%02X%02X%02X%02X", 
                Flagrand, macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]);
        header->wsa__MessageID =(char *)malloc( 100);
        memset(header->wsa__MessageID, 0, 100);
        strncpy(header->wsa__MessageID, _HwId, strlen(_HwId));
    
        if (was_Action != NULL)
        {
            header->wsa__Action =(char *)malloc(1024);
            memset(header->wsa__Action, '', 1024);
            strncpy(header->wsa__Action, was_Action, 1024);//"http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe";
        }
        if (was_To != NULL)
        {
            header->wsa__To =(char *)malloc(1024);
            memset(header->wsa__To, '', 1024);
            strncpy(header->wsa__To,  was_To, 1024);//"urn:schemas-xmlsoap-org:ws:2005:04:discovery";    
        }
        soap->header = header;
        return soap;
    } 
    
    int ONVIF_ClientDiscovery( )
    {
        int FoundDevNo = 0;
        int retval = SOAP_OK;
        wsdd__ProbeType req;       
        struct __wsdd__ProbeMatches resp;
        wsdd__ScopesType sScope;
        struct SOAP_ENV__Header header;
        struct soap* soap;
        
    
        const char *was_To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery";
        const char *was_Action = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe";
        //IP Adress and PortNo, broadCast    
        const char *soap_endpoint = "soap.udp://239.255.255.250:3702/";
    
        //Create new soap object with info
        soap = ONVIF_Initsoap(&header, was_To, was_Action, 10);
        
        soap_default_SOAP_ENV__Header(soap, &header);
        soap->header = &header;
    
        soap_default_wsdd__ScopesType(soap, &sScope);
        sScope.__item = "";
        soap_default_wsdd__ProbeType(soap, &req);
        req.Scopes = &sScope;
        req.Types = ""; //"dn:NetworkVideoTransmitter";
        
        //sent the message broadcast and wait
        retval = soap_send___wsdd__Probe(soap, soap_endpoint, NULL, &req);            
        while (retval == SOAP_OK)
        {
            retval = soap_recv___wsdd__ProbeMatches(soap, &resp);
            if (retval == SOAP_OK) 
            {
                if (soap->error)
                {
                    printf("[%d]: recv soap error :%d, %s, %s
    ", __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap)); 
                    retval = soap->error;
                }
                else //we find a device
                {
                    FoundDevNo ++;
                    if (resp.wsdd__ProbeMatches->ProbeMatch != NULL && resp.wsdd__ProbeMatches->ProbeMatch->XAddrs != NULL)
                    {
                        printf("****** No %d Devices Information ******
    ", FoundDevNo );
                        printf("Device Service Address  : %s
    ", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);    
                        printf("Device EP Address       : %s
    ", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);  
                        printf("Device Type             : %s
    ", resp.wsdd__ProbeMatches->ProbeMatch->Types);  
                        printf("Device Metadata Version : %d
    ", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);  
                        //sleep(1);
                    }
                }
            }
            else if (soap->error)  
            {  
                if (FoundDevNo == 0)
                {
                    printf("No Device found!
    "); 
                    retval = soap->error;  
                }
                else 
                {
                    printf("Search end! Find %d Device! 
    ", FoundDevNo);
                    retval = 0;
                }
                break;
            }  
        }
    
        soap_destroy(soap); 
        soap_end(soap); 
        soap_free(soap);
        
        return retval;
    }
    
    int main(void )
    {
    
        if (ONVIF_ClientDiscovery() != 0 )
        {
            printf("discovery failed!
    ");
            return -1;
        }
    
        return 0;
    }

    参考文献:

    【1】https://blog.csdn.net/laczf/article/details/47731917

    【2】https://baike.baidu.com/item/%E5%A4%9A%E6%92%AD/6867723?fr=aladdin

    【3】https://www.cnblogs.com/jingliming/p/4477264.html#_lab2_1_0

    【4】https://blog.csdn.net/saloon_yuan/article/details/27524875

  • 相关阅读:
    Code samples from Microsoft (AllInOne Code Framework) 微软一站式示例代码库
    spring bean属性property、ref使用方式(转载)
    spring 注入原理
    jQuery LigerUI 插件介绍及使用之ligerGrid
    spring小例子
    spring依赖注入原理
    SpringMVC关键问题讲解
    修改SVN的IP地址
    SpringMVC入门实例及详细讲解
    jQuery LigerUI使用教程入门篇
  • 原文地址:https://www.cnblogs.com/ziyu-trip/p/8724725.html
Copyright © 2011-2022 走看看