项目中需要用到检测设备的外网连接状态,第一时间就想到了ping,平时在电脑上检测网络是否通也是使用ping的,比如:
- ping 路由器ip可以检测局域网线路是否通
- ping 114.114.114.114等外网ip就可以检测外网网络是否通
- ping www.baidu.com等域名可以检测DNS是否配置成功
虽然在C程序中可以直接创建子进程调用shell命令的ping并等待分析返回的打印信息,但是创建进程开销比较大,不适合频繁调用。于是决定使用socket实现ping的功能。
ping是发送ICMP数据包也接收响应数据,可以参考busybox中的ping.c的源码。
1、初始化socket
int sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
if(sockfd!=0){
perror("RAW socket created error");
return -1;
}
//不阻塞
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
2、发送ICMP数据包
//sendbuf malloc合适的大小
//datalen=56;
//pid_t pid = getpid();
/*发送ping消息*/
int send_ping(char *sendbuf,int datalen,int sockfd,struct sockaddr_in *dest,pid_t pid)
{
int len=0;
struct icmp *icmp;
icmp=(struct icmp*)sendbuf;
icmp->icmp_type=ICMP_ECHO; //设置类型为ICMP请求报文
icmp->icmp_code=0;
icmp->icmp_cksum=0;
icmp->icmp_seq=0; //序号
icmp->icmp_id=pid; //设置当前进程ID为ICMP标示符
len=ICMP_HEADSIZE+datalen;
memset(icmp->icmp_data,0xff,datalen);
gettimeofday((struct timeval *)icmp->icmp_data,NULL); /* 获取当前时间*/
icmp->icmp_cksum=cal_chksum( (unsigned short *)icmp,len);
if(sendto(sockfd,sendbuf,len,0,(struct sockaddr *)dest,sizeof (struct sockaddr))<0) /*经socket传送数据*/
{
printf("error : ping sendto error\n");
return -1;
}
return 0;
}
3、接收响应数据
//recvbuf malloc合适的大小
//timeout_ms 接收超时时间,单位毫米
//GetCurrentTimeStamp 是封装好的一个获取当前时间毫秒的函数,可以使用gettimeofday()函数实现
/*接收程序发出的ping命令的应答*/
//return 0则表示收到响应数据,ping通了
int recv_reply(char *recvbuf,int sockfd,struct sockaddr_in *from,struct timeval *recvtime,pid_t pid, int timeout_ms)
{
int n=0;
int len=0;
//当前时间, 单位ms
unsigned int tNowTime = GetCurrentTimeStamp();
unsigned int tEndTime = tNowTime + timeout_ms;
int nrecv = 1; //
len = sizeof(struct sockaddr_in); /*发送ping应答消息的主机IP*/
while(nrecv>0)
{
tNowTime = GetCurrentTimeStamp();
if(tNowTime>tEndTime){
__ERR("ping time out\n");
return -1;
}
/*经socket接收数据,如果正确接收返回接收到的字节数,失败返回0.*/
if((n=recvfrom(sockfd, recvbuf, PING_BUF_SIZE, 0, (struct sockaddr *)from, &len))<=0)
{
continue;
}
gettimeofday(recvtime, NULL); /*记录收到应答的时间*/
if(handle_pkt(recvbuf,n,from,recvtime,pid)<0) /*接收到错误的ICMP应答信息*/
{
continue;
}
nrecv--;
break;
}
return 0;
}
/*ICMP应答消息处理*/
int handle_pkt(char *recvbuf,int datalen,struct sockaddr_in *from,struct timeval *recvtime,pid_t pid)
{
struct ip *ip=(struct ip *)recvbuf;
int iphdrlen=ip->ip_hl<<2; //求ip报头长度,即ip报头的长度标志乘4
struct icmp *icmp=(struct icmp *)(recvbuf+iphdrlen); //越过ip报头,指向ICMP报头
datalen-=iphdrlen; //ICMP报头及ICMP数据报的总长度
if( datalen<8)
return -1;
if((icmp->icmp_type!=ICMP_ECHOREPLY) || (icmp->icmp_id!=pid))
return -1;
printf("recv icmp reply from %s,icmp_id=%d,icmp_seq=%d\n",inet_ntoa(from->sin_addr),icmp->icmp_id,icmp->icmp_seq);
return 0;
}