背景:
想实现一个在没外网的时候就自动重启路由器的功能。
又不想用ping命令,因为在代码里调用system(“ping”); 可能会比较耗时,得单开线程。于是找了个实现ICMP协议的代码。
参考:https://blog.csdn.net/qivan/article/details/7237051
代码:
#include <stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<netinet/ip_icmp.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/time.h>
#include<unistd.h>
#include<netdb.h>
#include<string.h>#define PACKET_SIZE 4096#define ERROR 0#define SUCCESS 1//效验算法(百度下有注释,但是还是看不太明白)
unsignedshort cal_chksum(unsignedshort *addr,int len)
{int nleft=len;int sum=0;
unsignedshort *w=addr;
unsignedshort answer=0;while(nleft >1)
{
sum+= *w++;
nleft-=2;
}if( nleft ==1)
{*(unsignedchar *)(&answer) = *(unsignedchar *)w;
sum+= answer;
}
sum= (sum >>16) + (sum &0xffff);
sum+= (sum >>16);
answer= ~sum;return answer;
}// Ping函数int ping(char *ips,int timeout)
{struct timeval *tval;int maxfds =0;
fd_set readfds;struct sockaddr_in addr;struct sockaddr_infrom;// 设定Ip信息
bzero(&addr,sizeof(addr));
addr.sin_family= AF_INET;
addr.sin_addr.s_addr= inet_addr(ips);#if 1int sockfd;// 取得socket 。 如果没加sudo 这里会报错
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);if (sockfd <0)
{
printf("ip:%s,socket error\n",ips);return ERROR;
}struct timeval timeo;// 设定TimeOut时间
timeo.tv_sec = timeout /1000;
timeo.tv_usec= timeout %1000;if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeo,sizeof(timeo)) == -1)
{
printf("ip:%s,setsockopt error\n",ips);return ERROR;
}char sendpacket[PACKET_SIZE];char recvpacket[PACKET_SIZE];// 设定Ping包
memset(sendpacket,0,sizeof(sendpacket));
pid_t pid;// 取得PID,作为Ping的Sequence ID
pid=getpid();struct ip *iph;struct icmp *icmp;
icmp=(struct icmp*)sendpacket;
icmp->icmp_type=ICMP_ECHO;//回显请求
icmp->icmp_code=0;
icmp->icmp_cksum=0;
icmp->icmp_seq=0;
icmp->icmp_id=pid;
tval= (struct timeval *)icmp->icmp_data;
gettimeofday(tval,NULL);
icmp->icmp_cksum=cal_chksum((unsignedshort *)icmp,sizeof(struct icmp));//校验int n;// 发包 。可以把这个发包挪到循环里面去。
n = sendto(sockfd, (char *)&sendpacket,sizeof(struct icmp),0, (struct sockaddr *)&addr,sizeof(addr));if (n <1)
{
printf("ip:%s,sendto error\n",ips);return ERROR;
}// 接受// 由于可能接受到其他Ping的应答消息,所以这里要用循环while(1)
{// 设定TimeOut时间,这次才是真正起作用的
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
maxfds= sockfd +1;
n=select(maxfds, &readfds, NULL, NULL, &timeo);if (n <=0)
{
printf("ip:%s,Time out error\n",ips);
close(sockfd);return ERROR;
}// 接受
memset(recvpacket,0,sizeof(recvpacket));int fromlen =sizeof(from);
n= recvfrom(sockfd, recvpacket,sizeof(recvpacket),0, (struct sockaddr *)&from, (socklen_t *)&fromlen);
printf("recvfrom Len:%d\n",n);if (n <1)
{return ERROR;
}char *from_ip = (char *)inet_ntoa(from.sin_addr);// 判断是否是自己Ping的回复if (strcmp(from_ip,ips) !=0)
{
printf("NowPingip:%s Fromip:%s NowPingip is not same to Fromip,so ping wrong!\n",ips,from_ip);return ERROR;
}
iph= (struct ip *)recvpacket;
icmp=(struct icmp *)(recvpacket + (iph->ip_hl<<2));
printf("ip:%s,icmp->icmp_type:%d,icmp->icmp_id:%d\n",ips,icmp->icmp_type,icmp->icmp_id);// 判断Ping回复包的状态if (icmp->icmp_type == ICMP_ECHOREPLY && icmp->icmp_id == pid)//ICMP_ECHOREPLY回显应答 {// 正常就退出循环
printf("icmp succecss ............. \n");break;
}else
{// 否则继续等continue;
}
}#endifreturn SUCCESS;
}int main()
{#if 1char cPing[16];
printf("Please input ping IP:");
scanf("%s",cPing);#elsechar *cPing ="192.168.1.200";#endifif(ping(cPing,10000))
{
printf("Ping succeed!\n");
}else
{
printf("Ping wrong!\n");
}return0;
}
实际效果:

补充说明:
0)直接用参考链接上的代码时编译不过,不知道是不是因为我用的是cpp,没太深究。
1)实际使用的时候需要加上sudo,不然在创建套接字那个地方会报错。我还没想好怎么在代码里用sudo,(因为实际项目运行起来是不需要加sudo的)。
正文完