需求:设备的MAC地址出厂时已经固话号,IP地址可以由客户随便设置。相机一接上网线,无论其IP设置为多少,都可以通过上位机软件通过局域网将其搜索出来。
先来介绍几个概念:
广播域
- 广播域是网络中能接收任一台主机发出的广播帧的所有主机集合。也就是说,如果广播域内的其中一台主机发出一个广播帧,同一广播域(局域网)内所有的其它主机都可以收到该广播帧。
广播域的计算
- 如何知道一台主机是属于哪一个广播域呢?其实计算很简单,只要用主机的IP地址与子网掩码进行与运算即可知道该主机属于哪一个广播域。
- 例如:一台主机的IP地址为192.168.23.150,子网掩码为255.255.255.0,那么它所属的广播域就是192.168.23.150&255.255.255.0=192.168.23.0。那么其它的在广播域192.168.23.0内的所有主机就可以到该设备发送的广播包。如果把子网掩码改为255.255.0.0,那么它所属的广播域就是192.168.23.150&255.255.0.0=192.168.0.0。那么其它的在广播域192.168.0.0内的所有主机都可以收到该设备发送的广播包。
广播地址的计算
- 要想相同广播域内的其它主机能收到的广播帧,还需要在发送广播包的时候指定当前所属广播域内的广播地址。广播地址的计算方法为子网掩码取反再与广播域进行或运算。
- 例如:如果主机当前所属广播域为192.168.0.0,子网掩码为255.255.0.0,那么广播地址则为192.168.255.255。
- ~(255.255.0.0) | 192.168.0.0 = 0.0.255.255 | 192.168.0.0 = 192.168.255.255
使用UDP进行跨网段广播
- 要使主机A发送的广播包能够被另一网段的主机B收到,那么只需要更改主机A的子网掩码使得与主机B在同一个广播域内,再使用新的广播域的广播地址发送广播包即可。
- 例如:要使用192.168.23.150发送广播包让192.168.27.135收到,只需要设置192.168.23.150的子网掩码为255.255.0.0,然后再使用广播地址192.168.255.255即可。
特别要指出的是:255.255.255.255是受限广播地址,不能使用该地址发送广播包
方式一
- 采用 p2p方式,编写一个服务端程序和客户端程序,客户端程序自动向服务端报告本机IP和mac地址,由服务端进行服务调度,实现局域网发现;
方式二
- 采用arp协议,对局域网内活动的主机进行发现,这种方式主要是通过对局域网内的arp捕获,分析处理arp包的内容进行判断,实现局域网发现;
方式三
- 采用组播的方式,客户端所有主机都自动加入到组播中,在组播中,凡是存在的IP都是活跃折,实现局域网发现。
实际情况:
- 采用第一种方式的话 ,需要连接到外网,很多时候,并不提供外网,所以第一种方案不可以;
- 采用第二种方案也是有着局限性,因为arp协议是采用广播的方式进行,在没有外面的情况下,不同网段需要路由器转发,但路由器默认过滤广播包。所有,只有最终路由器才知道目的IP的MAC地址,如果进行跨网段通信的话,需要特定支持跨网段通信的路由器。
下面详细来说下udp组播方式如何实现的
- 组播(Multicast)传输:在发送者和每一接收者之间实现点对多点网络连接。如果一台发送者同时给多个的接收者传输相同的数据,也只需复制一份的相同数据包。它提高了数据传送效率。减少了骨干网络出现拥塞的可能性。
- 在组播方式中,信息的发送者称为“组播源”,信息接收者称为该信息的“组播组”,支持组播信息传输的所有路由器称为“组播路由器”。加入同一组播组的接收者成员可以广泛分布在网络中的任何地方,即“组播组”没有地域限制。需要注意的是,组播源不一定属于组播组,它向组播组发送数据,自己不一定是接收者。多个组播源可以同时向一个组播组发送报文。
- 假设只有 Host B、Host D 和Host E 需要信息,采用组播方式时,可以让这些主机加入同一个组播组(Multicast group),组播源向该组播组只需发送一份信息,并由网络中各路由器根据该组播组中各成员的分布情况对该信息进行复制和转发,最后该信息会准确地发送给Host B、Host D 和Host E。
下面附上boost.asio的组播(Multicast)实现:
http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/example/cpp03/multicast/sender.cpp
http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/example/cpp03/multicast/receiver.cpp
- client.cpp
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
/*
* 结论
* socket默认不支持发送广播报文,通过SO_BROADCAST选项的设置,开启广播发送功能。简单总结一下广播报文收发的规律:
* 1. 客户端socket开启SO_BROADCAST选项后才能发送广播to:255.255.255.255报文,否则调用sendto发送失败,会报错;
* 2. 服务端无需开启SO_BROADCAST,就可以接受广播数据包,SO_BROADCAST只是针对发送端的设置;
* 3. 服务端bind单播地址时,只接收目的地址为单播地址的报文,广播数据包不接收;
* 4. 服务端bind广播地址时,只接收客户端的广播数据,单播数据包不接收;
* 5. 服务端bind通用地址INADDR_ANY时,既能够接收客户端的单播报文,也能接收广播报文;
*/
int main(int argc, char **argv)
{
int sockfd, n, opt;
char recvline[256], sendbuf[256];
struct sockaddr_in servaddr, cliaddr;
//*****@gp_developer_server:~/test_code/linux系统udp单播广播$ ./udp_client 255.255.255.255
if (argc != 2)
{
printf("usage: argc != 2 %s <IPaddress>\n", argv[0]);
return 0;
}
printf("argv[0] = [%s]\n", argv[0]);
// 创建socket
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
printf("socket error\n");
return 0;
}
printf("socket create is suc... sockfd=[%d]\n", sockfd);
// bind本地ip和端口
bzero(&cliaddr, sizeof(struct sockaddr_in));
cliaddr.sin_family = AF_INET;
if (inet_pton(AF_INET, "192.168.50.10", &cliaddr.sin_addr) < 0)
{
printf("inet_pton error for cliaddr\n");
return 0;
}
printf("inet_pton is suc... for cliaddr=192.168.50.10\n");
if ((n = bind(sockfd, (struct sockaddr *)&cliaddr, sizeof(struct sockaddr))) < 0)
{
printf("bind failed\n");
return 0;
}
printf("bind is suc...\n");
// 设置广播属性
opt = 1;
if ((n = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt))) < 0)
{
printf("setsockopt SO_BROADCAST failed\n");
return 0;
}
printf("setsockopt SO_BROADCAST is suc...\n");
// sendto server地址和端口
bzero(&servaddr, sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(10000);
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)
{
printf("inet_pton error\n");
return 0;
}
printf("inet_pton is suc...\n");
// 发送数据
strncpy(sendbuf, "hello", sizeof(sendbuf));
if ((n = sendto(sockfd, sendbuf, sizeof(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(struct sockaddr))) < 0)
{
printf("sendto failed\n");
return 0;
}
printf("sendto is suc...\n");
return 0;
}
- server.cpp文件
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char **argv)
{
int sockfd, opt, n;
struct sockaddr_in servaddr;
char buff[256];
socklen_t cliaddrlen = sizeof(struct sockaddr_in);
// 创建socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 绑定本地ip和端口
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
char* ch = argv[1];
switch (*ch)
{
case 'a':
{
// 设置任意地址
servaddr.sin_addr.s_addr = htons(INADDR_ANY);
}
break;
case 'b':
{
// 设置固定ip地址
inet_pton(AF_INET, "192.168.50.10", &(servaddr.sin_addr));
}
break;
case 'c':
{
// 设置广播地址
inet_pton(AF_INET, "255.255.255.255", &(servaddr.sin_addr));
}
break;
default:
{
printf("agrv is error ch=[%s]", ch);
}
break;
}
// 绑定端口
servaddr.sin_port = htons(10000);
if ((n = bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0)
{
printf("bind failed\n");
return -1;
}
/* 服务器端不需要设置广播属性,因为这边不发送广播数据
opt = 1;
if ((n = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt))) < 0) {
printf("setsockopt eror");
}
*/
for (; ; )
{
struct sockaddr_in cliaddr;
bzero(&cliaddr, sizeof(cliaddr));
recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&cliaddr, &cliaddrlen);
printf("recv udp from:%s buff=[%s]\n", inet_ntoa(cliaddr.sin_addr), buff);
}
return 0;
}