Linux C++ 实现一个简易版的ping (也就是ICMP协议)

背景:

想实现一个在没外网的时候就自动重启路由器的功能。

又不想用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

//效验算法(百度下有注释,但是还是看不太明白)
unsigned short cal_chksum(unsigned short *addr, int len)
{
int nleft=len;
int sum=0;
unsigned short *w=addr;
unsigned short answer=0;

while(nleft > 1)
{
    sum += \*w++;
    nleft \-= 2;
}

if( nleft == 1)
{       
    \*(unsigned char \*)(&answer) = \*(unsigned char \*)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\_in from;  
// 设定Ip信息  
bzero(&addr,sizeof(addr));  
addr.sin\_family \= AF\_INET;  

addr.sin\_addr.s\_addr \= inet\_addr(ips);  

#if 1
int 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((unsigned short \*)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;  
    }  
} 

#endif
return SUCCESS;
}

int main()
{
#if 1
char cPing[16];
printf(“Please input ping IP:”);
scanf(“%s”,cPing);
#else
char *cPing = “192.168.1.200”;
#endif
if(ping(cPing,10000))
{
printf(“Ping succeed!\n”);
}
else
{
printf(“Ping wrong!\n”);
}

return 0;    

}

实际效果:

补充说明:

0)直接用参考链接上的代码时编译不过,不知道是不是因为我用的是cpp,没太深究。

1)实际使用的时候需要加上sudo,不然在创建套接字那个地方会报错。我还没想好怎么在代码里用sudo,(因为实际项目运行起来是不需要加sudo的)。