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的)。