設備發現協議
網絡環境下設備發現是一種比較常見的應用,比如查找打印機與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選項進行設置,詳見下表:
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層的,其選項值和含義見下表:
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
設備發現協議