项目目前有个需求,需要在多网口情况下自动切换到网络最好的节点
总结一下代码逻辑:
-
流程:
1.判断当前传入网址为ip还是域名,如果为域名情况下,讲域名解析成ip
dstAddr = inet_addr(dst);
if (-1 == dstAddr)
{
struct hostent *he;
he = gethostbyname(dst);
if (NULL == he || 2 != he->h_addrtype)
{
return -1;
}
inaddr = (struct in_addr *)he->h_addr;
dstAddr = inaddr->s_addr;
}
2.获取当前网络节点ip
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock <= 0)
{
return ret;
}
strncpy(ifr.ifr_name, iface, strlen(iface));
ret = ioctl(sock, SIOCGIFADDR, &ifr);
if (0 == ret)
{
sin = (struct sockaddr_in *)&ifr.ifr_addr;
if (0 != sin->sin_addr.s_addr)
{
memcpy(ipAddr, sin, sizeof(struct sockaddr_in));
ret = 0;
}
}
close(sock);
3.创建icmp套接字,并填装数据
sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock <= 0)
{
printf("sock create failed, errno=%d\n", errno);
return ret;
}
icmpInfo.icmp_type = ICMP_ECHO;
icmpInfo.icmp_code = 0;
icmpInfo.icmp_cksum = 0;
icmpInfo.icmp_seq = 1;
icmpInfo.icmp_id = icmpID;
icmpInfo.icmp_cksum = in_cksum(&icmpInfo, sizeof(icmpInfo));
4.设置套接字属性,需要bind绑定当前网口地址
一开始没有使用SO_BINDTODEVICE这个属性,发现通过ppp拨号的4g模组始终不能ping成功,但是用命令行的ping -I ppp0可以成功,查看原码后才发现这个属性一定要设置
将ping源码中设置的属性全部都拷贝过来
opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
opt = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(int));
opt = 2000;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &opt, sizeof(int));
opt = 2000;
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &opt, sizeof(int));
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &src, sizeof(src));
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface, strlen(iface));
//注意这个属性一定要设置
if (0 != setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)))
{
printf("bindto iface %s failed, errno %d\n", iface, errno);
close(sock);
return ret;
}
if (bind(sock, (struct sockaddr *)&src, sizeof(src)) < 0)
{
printf("bind failed\n");
close(sock);
return ret;
}
5.通过select等待返回接收,这里最好要判断接收到的icmp的id是否和发出去的一样,并且select函数也可以获取到小号时间
while (select(sock+1, &rset, NULL, NULL, &recvTime) > 0)
{
rlen = recvfrom(sock, recvBuffer, 1024, 0, (struct sockaddr *)&dstAddr, &addrlen);
if (rlen <= 0)
{
break;
}
dstIp = (struct ip *)recvBuffer;
offset = dstIp->ip_hl << 2;
icmpRes = (struct icmp *)(&recvBuffer[offset]);
if ((ICMP_ECHOREPLY == icmpRes->icmp_type) && (icmpID == icmpRes->icmp_id))
{
ret = 0;
*timeoutMS = waitTime - ((recvTime.tv_sec * 1000) + (recvTime.tv_usec / 1000));
break;
}
}
异常处理:
gethostbyname这个系统函数需要注意,不支持可重入,并且会阻塞(gethostbyname_r同样如此)。
处理方法
1.每个gethostbyname函数前加锁,/etc/resolv.conf中设置超时时间
#实际测试中gethostbyname并非1秒结束,猜测应该是和resolv.conf中的多个nameserver有关,每个nameserver都是1s
options timeout:1 attempts:1
2.移植开源的dns客户端,如adns,移植很方便,唯一的不足就是这方面文档不多,需要自己理解
3.如果此时网线、wifi或者4g断开,可以用route -n查看当前网关情况,如果当前网络节点已经断开,则删除相应路由,这样gethostbyname会直接报错返回,比如
route del default gw 192.168.100.254 dev eth0