前言
由于项目需要,了解到这部分内容,查了很多资料,没看到合适的。最后参考网上资料再加上自己的尝试,终于解决问题。
需求:NVR 在搜索IPC时,判断我们的IPC是否和NVR在同一网段,如果是检查是否有IP 冲突,如果有IP冲突,重新分配一个可用的IP。如果不在同一网段,重新分配一个在同一网段的可用IP。(且有多个 IPC 同时存在的情况)
解决方案:首先通过 onvif 协议获取 NVR IP 地址。假设 NVR 是C类网络,并且 为192.168.x.x 格式。且子网掩码为255.255.255.0 。得到 NVR IP 以后,判断是否符合假设如果不是不进行进一步处理。然后拿本 IPC 的 IP 和NVR 的 IP 分别和子网掩码做按位与操作,然后比较二者的值。如果在同一网段,检查是否有IP冲突,有冲突的话获取本地可用 IP 并分配;然后再检查,如此循环最多五次。如果不在同一网段获取本地可用IP并分配,检查是否有 IP 冲突,如果有冲突,再分配再检查,循环最多五次。
具体函数实现
1、判断本机是否和局域网内其它设备有 IP 冲突
/*description check if there is ip conflict
*return 1 ip conflict, 0 not
*/
int check_if_ip_conflict()
{
char arrcCmd[60] = {0};
char *pIP = NULL;
int iConflictFlag = 0;
struct in_addr stIPLocal = g_pIPCamSystemInfo->lan_config.net.ip; // this var is an environmnet which holds the IP address of current device.
pIP = inet_ntoa(stIPLocal);
printf("current local ip is %s\n", pIP);
sprintf(arrcCmd, "arping -D -c 1 %s > /mnt/mtd/etc/null", pIP);
int ret = system(arrcCmd);
if(-1 != ret && 1 == WEXITSTATUS(ret))
{
iConflictFlag = 1;
}
return iConflictFlag;
}
注:实现主要利用了 arping 命令。 -D 检测是否有 IP 冲突。原理是 arping 向局域网内发送广播时,自己不会回复。如果 arping 了自己的 IP 收到回复,说明有其它设备使用了相同的IP。-c 指定发送请求包的次数,指定为最小值 1 减少arping 命令的阻塞时间。这些参数的意思都可在 linux shell 中端输入 arping 获得。> 意思是把命令的结果重定向到文件 /mnt/mtd/etc/null 中去,且覆盖之前的信息。
arping -D 收到回复时返回 1 。这个可以在 shell 终端测试,shell 命令的返回值,可以在执行完后,在终端输入 $? 获得。
WEXITSTATUS() 宏 通过 system 的返回值,获取shell 命令的返回值。
2、获取一个随机数作为主机号
static int get_random_host_num()
{
struct drand48_data randbuf;
struct timeval tv;
gettimeofday(&tv, NULL );
srand48_r(tv.tv_usec, &randbuf);
double x;
drand48_r(&randbuf, &x);
int iHostNum = (int)(x*1000000)%254 + 1; // range [1,254]
printf("radom host num is %d\n",iHostNum);
if(iHostNum < 2)
{
iHostNum = 2; // ignore host num 1;
}
return iHostNum;
}
注:由于有多个 IPC 同时在查找可用 IP,为避免每个 IPC 查找到的 IP 互相重复,使用随机作为主机号以减少重复的概率。获取随机数也可以使用 srand() 和 rand() 这种简单的实现,drand48_r() 随机性更好一些。
3、查找局域网内的可用 IP
/*description find an available ip
*@iHostNum random number in [2,254] used as host number
*@ucIPPart3 the 3rd part of a numbers-and-dots notation ip address.
*@pstNet a struct to store ip and gateway
*return 1 available ip has been found, 0 not.
*/
typedef struct
{
struct in_addr ip;
struct in_addr gateway;
} IP_GATE_LAN;
int find_available_ip(int iHostNum, const unsigned char ucIPPart3, IP_GATE_LAN *pstNet)
{
char arrcCmd[60] = {0};
char arrcDottedIP[16] = {0};
int iFindFlag = 0;
// traverse ip addresses
for(int i = iHostNum; i < 255; i++)
{
memset(arrcCmd, 0, sizeof(arrcCmd));
sprintf(arrcDottedIP, "192.168.%d.%d", ucIPPart3, i);
sprintf(arrcCmd, "arping -c 1 %s > /mnt/mtd/etc/null", arrcDottedIP);
int ret = system(arrcCmd);
if(-1 != ret && 1 == WEXITSTATUS(ret)) // here dest ip is not alive. ping returns 0 if dest host is alive, 1 if not.
{
iFindFlag = 1;
printf("the first aviliable IP is %s\n", arrcDottedIP);
break;
}
}
if(1 == iFindFlag)
{
char arrcGateway[16] = {0};
sprintf(arrcGateway,"192.168.%d.1", ucIPPart3);
pstNet->ip.s_addr = inet_addr(arrcDottedIP);
pstNet->gateway.s_addr = inet_addr(arrcGateway);
}
return iFindFlag;
}
注:这里的关键还是对 arping 命令的使用,开始想使用 ping 命令后来发现 ping 命令无法跨网段通信。网上看到有人说 arping 命令,测试可以。
arping x.x.x.x 如果主机 是 alive ,只需要 1 到 2 毫秒, not alive , -c 1,应该在 1 秒以内,这是我能控制的最短时间了。-w 参数可以指定超时时间,但最小值只能是 1 秒。arping 已经可以 send 两次了。总体上效率还是比较高的。
4、 设置 IP 和 gateway
int set_ip_and_gateway(const IP_GATE_LAN *pstNet);
注: 这里主要调用了公司的函数实现,就不列出来了。
5、循环检测 IP 冲突 和 查找并设置 IP
/*decription check if the IP of ipc is in same net segment with that of nvr,
*if not find an available ip addr and set.
*@cuiIPNVR ip of nvr from onvif
*/
int keep_ip_with_nvr_net(const unsigned int cuiIPNVR)
{
unsigned int uiNetMask = inet_addr("255.255.255.0");
unsigned int uiIPLocal = g_pIPCamSystemInfo->lan_config.net.ip.s_addr;
IP_GATE_LAN stNet = {0};
int iRet = 0; //return value;
const unsigned char *pcucIP = (const unsigned char *)&cuiIPNVR;
printf("nvr ip is %d.%d.%d.%d\n", *pcucIP, *(pcucIP + 1), *(pcucIP + 2), *(pcucIP + 3));
unsigned char ucIPPart3 = *(pcucIP + 2);
int iHostNum = 2;
int i = 0;
if((uiNetMask & uiIPLocal) == (uiNetMask & cuiIPNVR))
{
printf("local ip is in the same net segment with nvr ip\n");
// check if ip conflict
while(check_if_ip_conflict())
{
i++;
printf("while has cycled %d times \n", i);
iHostNum = get_random_host_num();
iRet = find_available_ip(iHostNum, ucIPPart3, &stNet);
if(1 == iRet)
{
set_ip_and_gateway(&stNet);
}
else
{
iRet = find_available_ip(2, ucIPPart3, &stNet);
if(1 == iRet)
{
set_ip_and_gateway(&stNet);
}
else
{
printf("There is no available IP left in LAN\n");
return 1;
}
}
if(i >= 5)
{
printf("ip conflict can not handle automaticaly,please handle manualy\n");
return -1;
}
}
return 0;
}
else
{
do
{
i++;
printf("do while has cycled %d times \n", i);
iHostNum = get_random_host_num();
iRet = find_available_ip(iHostNum, ucIPPart3, &stNet);
if(1 == iRet)
{
set_ip_and_gateway(&stNet);
}
else
{
iRet = find_available_ip(2, ucIPPart3, &stNet);
if(1 == iRet)
{
set_ip_and_gateway(&stNet);
}
else
{
printf("There is no available IP left in LAN\n");
return 1;
}
}
if(i >= 5)
{
printf("ip conflict can not handle automaticaly,please handle manualy\n");
return -1;
}
}
while(check_if_ip_conflict());
return 0;
}
return 0;
}
/*added by joden 20200514 end*/
总结
此博客的实现也是本人摸着石头过河一点点摸索出来的,由于本人对网络编程了解甚少,可能有的代码看起来比较蠢。不过不管怎样我最终实现了客户的需求,写出了让自己满意的代码。在此记下,以便日后回顾,也希望给有这方面需求的新人一点帮助,希望别人不要像我那样,很久都找不到可以参考的资料。
Joden 20200517