1. 程式人生 > >設備發現協議

設備發現協議

print sch version ipp ada -s prot ans 信息

  網絡環境下設備發現是一種比較常見的應用,比如查找打印機與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\n", ip); printf("mask: %s\n", mask); ip[3] = \0; ip[7] = \0; ip[11] = \0; mask[3] = \0; mask[7] = \0; mask[11] = \0; 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] = \0; 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...\n"<<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...\n"<<endl;
            return -1;
        }
        else 
        {
            printf("ok\n");
        }
    }
    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\t",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\n",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\n", __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, \0, 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, \0, 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\n", __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 ******\n", FoundDevNo );
                    printf("Device Service Address  : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);    
                    printf("Device EP Address       : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);  
                    printf("Device Type             : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->Types);  
                    printf("Device Metadata Version : %d\r\n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);  
                    //sleep(1);
                }
            }
        }
        else if (soap->error)  
        {  
            if (FoundDevNo == 0)
            {
                printf("No Device found!\n"); 
                retval = soap->error;  
            }
            else 
            {
                printf("Search end! Find %d Device! \n", 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!\n");
        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

設備發現協議