C++实现一个ping命令

今天利用C++实现一个自己的ping命令,首先我们在Linux下运行下ping命令,看它完成了那些功能:


1.向目标IP发送一个64字节的ICMP请求包,然后收到目标IP的一个ICMP应答包,并逐个打印发送请求包到接收应答包的时间

2.最后打印出一共发送多少个ICMP包,是否有丢包,收发的总时间


下面就逐步实现我的一个ping命令,有不足的地方欢迎留言!

一、明确几个概念:

1.1ICMP报文作用?

ICMP是(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。

1.2ICMP的报文格式是如何?

ICMP报文包含在IP数据报中,属于IP的数据,IP头部就在ICMP报文的前面,一个ICMP报文包括IP头部(20字节)、ICMP头部(8字节)和ICMP报文(即下图的选项数据)。


IP头部的Protocol值为1就说明这是一个ICMP报文,下面逐个介绍ICMP报文组成:

各种ICMP报文的前4个字节(32bits)都是三个长度固定的字段(见上图);

type类型字段(8)code代码字段(8)checksum校验和字段(16);

8bits类型和8bits代码字段:一起决定了ICMP报文的类型。常见的有:

类型8、代码0:回显请求类型0、代码0:回显应答;类型11、代码0:超时;

16bits校验和字段:包括数据在内的整个ICMP数据包的校验和,其计算方法和IP头部校验和的计算方法是一样的;

对于ICMP回显请求和应答报文来说(见上图),接下来是16bits标识符字段:一般用于标识本ICMP进程。最后是16bits序列号字段:用于判断回显应答数据报顺序。

在Linux中ICMP数据结构(<netinet/ip_icmp.h>)定义如下:

[cpp]  view plain  copy
  1. <span style="color:#555555;">struct icmp    
  2. {    
  3.   u_int8_t  icmp_type;  /* type of message, see below */ 
  4.   u_int8_t  icmp_code;  /* type sub code */
  5.   u_int16_t icmp_cksum; /* ones complement checksum of struct */ 
  6.   union    
  7.   {    
  8.     u_char ih_pptr;     /* ICMP_PARAMPROB */    
  9.     struct in_addr ih_gwaddr;   /* gateway address */    
  10.     struct ih_idseq     /* echo datagram */    
  11.     {    
  12.       u_int16_t icd_id;    
  13.       u_int16_t icd_seq;    
  14.     } ih_idseq;    
  15.     u_int32_t ih_void;    
  16.     /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */    
  17.     struct ih_pmtu    
  18.     {    
  19.       u_int16_t ipm_void;    
  20.       u_int16_t ipm_nextmtu;    
  21.     } ih_pmtu;    
  22.     struct ih_rtradv    
  23.     {    
  24.       u_int8_t irt_num_addrs;    
  25.       u_int8_t irt_wpa;    
  26.       u_int16_t irt_lifetime;    
  27.     } ih_rtradv;    
  28.   } icmp_hun;    
  29. #define icmp_pptr   icmp_hun.ih_pptr    
  30. #define icmp_gwaddr icmp_hun.ih_gwaddr    
  31. #define icmp_id     icmp_hun.ih_idseq.icd_id
  32. #define icmp_seq        icmp_hun.ih_idseq.icd_seq
  33. #define icmp_void   icmp_hun.ih_void    
  34. #define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void    
  35. #define icmp_nextmtu    icmp_hun.ih_pmtu.ipm_nextmtu    
  36. #define icmp_num_addrs  icmp_hun.ih_rtradv.irt_num_addrs    
  37. #define icmp_wpa    icmp_hun.ih_rtradv.irt_wpa    
  38. #define icmp_lifetime   icmp_hun.ih_rtradv.irt_lifetime    
  39.   union    
  40.   {    
  41.     struct    
  42.     {    
  43.       u_int32_t its_otime;    
  44.       u_int32_t its_rtime;    
  45.       u_int32_t its_ttime;    
  46.     } id_ts;    
  47.     struct    
  48.     {    
  49.       struct ip idi_ip;    
  50.       /* options and then 64 bits of data */    
  51.     } id_ip;    
  52.     struct icmp_ra_addr id_radv;    
  53.     u_int32_t   id_mask;    
  54.     u_int8_t    id_data[1];    
  55.   } icmp_dun;    
  56. #define icmp_otime  icmp_dun.id_ts.its_otime    
  57. #define icmp_rtime  icmp_dun.id_ts.its_rtime    
  58. #define icmp_ttime  icmp_dun.id_ts.its_ttime    
  59. #define icmp_ip     icmp_dun.id_ip.idi_ip    
  60. #define icmp_radv   icmp_dun.id_radv    
  61. #define icmp_mask   icmp_dun.id_mask    
  62. #define icmp_data   icmp_dun.id_data
  63. };  </span>  

ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。因此我们还需知道IP报文的格式。

在Linux中,IP报头格式数据结构(<netinet/ip.h>)定义如下:

  1. struct ip  
  2.   {  
  3. #if __BYTE_ORDER == __LITTLE_ENDIAN  
  4.     unsigned int ip_hl:4;       /* header length */  
  5.     unsigned int ip_v:4;        /* version */  
  6. #endif  
  7. #if __BYTE_ORDER == __BIG_ENDIAN  
  8.     unsigned int ip_v:4;        /* version */  
  9.     unsigned int ip_hl:4;       /* header length */  
  10. #endif  
  11.     u_int8_t ip_tos;            /* type of service */  
  12.     u_short ip_len;         /* total length */  
  13.     u_short ip_id;          /* identification */  
  14.     u_short ip_off;         /* fragment offset field */  
  15. #define IP_RF 0x8000            /* reserved fragment flag */  
  16. #define IP_DF 0x4000            /* dont fragment flag */  
  17. #define IP_MF 0x2000            /* more fragments flag */  
  18. #define IP_OFFMASK 0x1fff       /* mask for fragmenting bits */  
  19.     u_int8_t ip_ttl;            /* time to live */  
  20.     u_int8_t ip_p;          /* protocol */  
  21.     u_short ip_sum;         /* checksum */  
  22.     struct in_addr ip_src, ip_dst;  /* source and dest address */  
  23.   };  

其中ping程序只使用以下数据:

  • IP报头长度IHL(Internet Header Length)以4字节为一个单位来记录IP报头的长度,是上述IP数据结构的ip_hl变量。
  • 生存时间TTL(Time To Live)以秒为单位,指出IP数据报能在网络上停留的最长时间,其值由发送方设定,并在经过路由的每一个节点时减一,当该值为0时,数据报将被丢弃,是上述IP数据结构的ip_ttl变量。
二、将程序的大致框架搭出来,将ICMP报文发送与接收的过程用程序的语言描述出来: 2.1新建文件ping.hpp,创建一个类

[cpp]  view plain  copy
  1. #ifndef _PING_H  
  2. #define _PING_H  
  3.   
  4. #include <iostream>  
  5. #include <string>  
  6. #include <string.h>  
  7. #include <netinet/ip_icmp.h>  
  8. #include <netdb.h>  
  9. #include <sys/socket.h>  
  10. #include <sys/types.h>  
  11. #include <sys/time.h>  
  12. #include <arpa/inet.h>  
  13. #include <unistd.h>  
  14. #include <signal.h>  
  15.   
  16. #define PACKET_SIZE     4096    
  17. #define SEND_DATA_LEN   56    
  18. #define ERROR           -1    
  19. #define SUCCESS         1    
  20. #define MAX_WAIT_TIME  3    
  21. #define MAX_NO_PACKETS  4   
  22.   
  23. using namespace std;  
  24.   
  25. class Cping  
  26. {  
  27. public:  
  28.     Cping(const char * ip, int timeout);    
  29.     Cping(const Cping& orig);    
  30.     virtual ~Cping();  
  31.   
  32. private:  
  33.     std::string m_strIp;            //用来保存用户输入的IP  
  34.     std::string m_copy_Ip;          //这个也是用来保存用户的输入IP,相当于一个备份  
  35.   
  36.     int m_nSend;                   //用来记录发送的数据包数量   
  37.     int m_nRecv;                   //用来记录接收到的数据包数量  
  38.     struct sockaddr_in m_dest_addr;     //用来保存目标IP的套接字地址  
  39.     struct sockaddr_in m_from_addr;     //用来保存来自目标IP的套接字地址  
  40.     char m_sendpacket[PACKET_SIZE];     //用于保存发送的ICMP包   
  41.     char m_recvpacket[PACKET_SIZE];     //用于保存接收的ICMP包  
  42.     struct timeval m_tvrecv;            //收到数据包的时间  
  43.     struct timeval m_begin_tvrecv;      //开始发送数据包的时间  
  44.     struct timeval m_end_tvrecv;        //最后一次收到的数据包的时间  
  45.     double m_dTotalResponseTimes;       //发送到接收所有数据包的时间  
  46.     int m_nSocketfd; //套接字  
  47.       
  48.     int m_nMaxTimeWait;         //每次接收数据包的最长等待时间  
  49. public:  
  50.     bool ping(int times);  
  51.     //创建关闭socket  
  52.     bool CreateSocket();    
  53.     bool CloseSocket();   
  54.     //收发一个IP包  
  55.     void send_packet(void);    
  56.     void recv_packet(void);  
  57.     //数据打包和拆包  
  58.     int pack(int pack_no);  
  59.     int unpack(char *buf, int len);  
  60.     //时间的计算以及信息打印  
  61.     void tv_sub(struct timeval *out, struct timeval *in);    
  62.     void statistics(int sig);  
  63.     //ICMP校验和计算函数  
  64.     static unsigned short cal_chksum(unsigned short *addr, int len);  
  65. };  
  66.   
  67. #endif  


2.2新建文件ping.cpp,将过程描述出来,实现ping函数,其他函数暂不实现
[cpp]  view plain  copy
  1.   

[cpp]  view plain  copy
  1. #include "ping.h"  
  2. //构造函数对参数的初始化  
  3. Cping::Cping(const char * ip, int timeout)  
  4. {  
  5.     m_strIp = ip;    
  6.     m_copy_Ip = ip;  
  7.   
  8.     m_nSend = 0;    
  9.     m_nRecv = 0;  
  10.       
  11.     m_dTotalResponseTimes = 0;  
  12.   
  13.     if(timeout > MAX_WAIT_TIME)  
  14.     {  
  15.         m_nMaxTimeWait = MAX_WAIT_TIME;  
  16.     }  
  17.     else  
  18.     {  
  19.         m_nMaxTimeWait = timeout;  
  20.     }  
  21. }  
  22.   
  23. Cping::~Cping()  
  24. {  
  25.     if(!CloseSocket())  
  26.     {  
  27.         cout<<"CloseSocket failed!"<<endl;  
  28.     }  
  29. }  
  30.   
  31. /*ping函数描述了ping的框架 
  32.  *1.创建一个套接字并获取(转换)目标IP地址 
  33.  *2.发送报文 
  34.  *3.接收报文 
  35.  *4.结束打印信息 
  36. */  
  37. bool Cping::ping(int times)  
  38. {  
  39.     //signal(SIGINT,statistics);  
  40.     if(!CreateSocket())  
  41.     {  
  42.         printf("CreateSocket failed!!!\n");  
  43.         return false;  
  44.     }  
  45.     printf("PING %s(%s): %d bytes data in ICMP packets.\n", m_strIp.c_str(),    
  46.                 m_copy_Ip.c_str(), SEND_DATA_LEN);   
  47.     while(times--)  
  48.     {  
  49.         send_packet();  
  50.         recv_packet();  
  51.         sleep(1);   //每秒发送一个报文  
  52.     }  
  53.     statistics(SIGINT);  
  54.     return true;  
  55. }  
  56.   
  57. //创建socket,并完成m_dest_addr结构体的赋值  
  58. bool Cping::CreateSocket()  
  59. {  
  60.     return true;      
  61. }  
  62.   
  63. //关闭套接字  
  64. bool Cping::CloseSocket()  
  65. {  
  66.   
  67. }  
  68.   
  69. //发送一个IP包  
  70. void Cping::send_packet(void)  
  71. {  
  72.   
  73. }  
  74.   
  75. //接收一个IP包  
  76. void Cping::recv_packet(void)  
  77. {  
  78.   
  79. }  
  80.   
  81. //构建一个ICMP数据包,对ICMP结构体成员变量赋值  
  82. int Cping::pack(int pack_number)  
  83. {  
  84.     int packsize;    
  85.     return packsize;                                //返回ICMP报文大小  
  86. }  
  87.   
  88. //获取到IP数据包以后,去除IP报头,完成ICMP报文的校验以及时间的提取  
  89. int Cping::unpack(char *buf, int len)  
  90. {  
  91.     return 1;  
  92. }  
  93.   
  94.   
  95. //时间的计算以及信息打印  
  96. void Cping::tv_sub(struct timeval *out, struct timeval *in)  
  97. {  
  98.   
  99.       
  100. }  
  101. //打印收发信息  
  102. void Cping::statistics(int sig)  
  103. {     
  104.    
  105. }  
  106.   
  107.   
  108. //ICMP校验和计算函数  
  109. unsigned short Cping::cal_chksum(unsigned short *addr, int len)  
  110. {  
  111.   
  112. }  

2.3新建文件main.cpp

[cpp]  view plain  copy
  1. #include "ping.h"  
  2. //没有用signal修改程序捕获到键盘中断信号SIGINT后打印statistics信息,所以用ping的一个参数限定ping的次数  
  3. int main(int argc,char *argv[])  
  4. {  
  5.     Cping Ping(argv[1],20);//向目标IP(argv[1])发送数据包以后,每次接收数据包的最长等待时间为20s  
  6.     Ping.ping(3);//向目标IP发送3个数据包  
  7. }   

2.4完成大体框架的搭建可以先编译运行,没有错误再去将每一个函数实现


三、将其他函数实现,下面贴出源代码

[cpp]  view plain  copy
  1. #include "ping.h"  
  2. //构造函数对参数的初始化  
  3. Cping::Cping(const char * ip, int timeout)  
  4. {  
  5.     m_strIp = ip;    
  6.     m_copy_Ip = ip;  
  7.   
  8.     m_nSend = 0;    
  9.     m_nRecv = 0;  
  10.       
  11.     m_dTotalResponseTimes = 0;  
  12.   
  13.     if(timeout > MAX_WAIT_TIME)  
  14.     {  
  15.         m_nMaxTimeWait = MAX_WAIT_TIME;  
  16.     }  
  17.     else  
  18.     {  
  19.         m_nMaxTimeWait = timeout;  
  20.     }  
  21. }  
  22.   
  23. Cping::~Cping()  
  24. {  
  25.     if(!CloseSocket())  
  26.     {  
  27.         cout<<"CloseSocket failed!"<<endl;  
  28.     }  
  29. }  
  30.   
  31. /*ping函数描述了ping的框架 
  32.  *1.创建一个套接字并获取(转换)目标IP地址 
  33.  *2.发送报文 
  34.  *3.接收报文 
  35.  *4.结束打印信息 
  36. */  
  37. bool Cping::ping(int times)  
  38. {  
  39.     //signal(SIGINT,statistics);  
  40.     if(!CreateSocket())  
  41.     {  
  42.         printf("CreateSocket failed!!!\n");  
  43.         return false;  
  44.     }  
  45.     printf("PING %s(%s): %d bytes data in ICMP packets.\n", m_strIp.c_str(),    
  46.                 m_copy_Ip.c_str(), SEND_DATA_LEN);   
  47.     while(times--)  
  48.     {  
  49.         send_packet();  
  50.         recv_packet();  
  51.         sleep(1);   //每秒发送一个报文  
  52.     }  
  53.     statistics(SIGINT);  
  54.     return true;  
  55. }  
  56.   
  57. //创建socket,并完成m_dest_addr结构体的赋值  
  58. bool Cping::CreateSocket()  
  59. {  
  60.       
  61.     char buf[2048];                         //gethostbyname_r函数临时的缓冲区,用来存储过程中的各种信息  
  62.     int errnop = 0;                         //gethostbyname_r函数存储错误码  
  63.     unsigned long inaddr;                   //用来保存网络字节序的二进制地址  
  64.     struct hostent hostinfo,*dest_phost;    //用于gethostbyname_r存放IP信息  
  65.     struct protoent *protocol;              //指向协议protoent的一个结构体  
  66.   
  67.     //1.创建一个socket  
  68.     //1.1通过协议名称icmp获取协议编号  
  69.     if ((protocol = getprotobyname("icmp")) == NULL)    
  70.     {    
  71.         printf("CreateSocket: getprotobyname failed:%d\n",errno);    
  72.           
  73.         return false;  
  74.     }  
  75.     //1.2创建原始套接字时,需要有root权限,防止应用程序绕过内建安全机制  
  76.     if(-1 == (m_nSocketfd = socket(AF_INET,SOCK_RAW,protocol->p_proto)))  
  77.     {  
  78.         printf("CreateSocket: create socket failed:%d\n",errno);    
  79.   
  80.         return false;     
  81.     }  
  82.       
  83.     //回收root权限,设置当前用户权限   
  84.     setuid(getuid());  
  85.       
  86.     //2.设置m_dest_addr结构体  
  87.     m_dest_addr.sin_family = AF_INET;  
  88.       
  89.     bzero(&(m_dest_addr.sin_zero),8);  
  90.       
  91.     //2.1检测用户输入的参数是主机名还是IP地址(点分十进制地址),并转化为网络字节序的二进制地址  
  92.     if((inaddr=inet_addr(m_strIp.c_str())) == INADDR_NONE)  
  93.     {  
  94.         if(gethostbyname_r(m_strIp.c_str(),&hostinfo,buf,sizeof(buf),&dest_phost,&errnop))  
  95.         {  
  96.             printf("CreateSocket: gethostbyname error %s failed:%d\n",m_strIp.c_str(),errnop);    
  97.   
  98.             return false;  
  99.         }  
  100.         else//输入的是主机名  
  101.         {  
  102.             m_dest_addr.sin_addr = *((struct in_addr *)dest_phost->h_addr);//?会不会有问题直接赋值  
  103.         }  
  104.     }  
  105.     else//输入的是IP地址  
  106.     {  
  107.         m_dest_addr.sin_addr.s_addr = inaddr;  
  108.     }  
  109.     //2.2将用户输入的IP地址保存下来,用于后续包的校验  
  110.     m_copy_Ip = inet_ntoa(m_dest_addr.sin_addr);  
  111.     /* 
  112.     if(inet_ntop(AF_INET,(void*)m_copy_Ip.c_str(),(char*)&(m_dest_addr.sin_addr.s_addr),sizeof(m_copy_Ip))) 
  113.     { 
  114.         printf("CreateSocket: inet_ntop error!,errno = %d\n",errno);   
  115.                
  116.         exit(1);     
  117.     } 
  118.     */  
  119.     return true;      
  120. }  
  121.   
  122. //关闭套接字  
  123. bool Cping::CloseSocket()  
  124. {  
  125.       
  126.     bool flag = false;  
  127.     if(m_nSocketfd)  
  128.     {  
  129.         close(m_nSocketfd);  
  130.         flag = true;  
  131.     }  
  132.     return flag;  
  133.   
  134. }  
  135.   
  136. //发送一个IP包  
  137. void Cping::send_packet(void)  
  138. {  
  139.     int packetsize;      //包的大小
  140.     packetsize = pack(m_nSend);  
  141.       
  142.     if((sendto(m_nSocketfd,m_sendpacket,packetsize,0,(const struct sockaddr*)&m_dest_addr,sizeof(m_dest_addr))) < 0)  
  143.     {  
  144.         printf("send_packet: send error :%d\n",errno);  
  145.     }  
  146.     m_nSend++;     //发送一个IP包以后,发送次数加1
  147. }  
  148.   
  149. //接收一个IP包  
  150. void Cping::recv_packet(void)  
  151. {  
  152.       
  153.     int fromlen,packetsize,n;  
  154.     while(m_nRecv < m_nSend)    
  155.     {  
  156.         struct timeval timeout;  
  157.         fd_set readfd;                 //定义一个文件集合
  158.         FD_ZERO(&readfd); //将该集合清空
  159.         FD_SET(m_nSocketfd,&readfd);   //将套接字描述符添加到readfd集合中
  160.         int maxfd = m_nSocketfd + 1;    //select监听的最大文件描述符
  161.         timeout.tv_sec = m_nMaxTimeWait; //设置select的超时等待时间  
  162.         timeout.tv_usec = 0;  
  163.           
  164.         n = select(maxfd,&readfd,NULL,NULL,&timeout);//用select实现非阻塞I/O  
  165.           
  166.         switch(n)  
  167.         {  
  168.             case 0:  
  169.                 printf("recv_packet: select time out :%d\n",errno);    
  170.                 break;  
  171.             case -1:  
  172.                 printf("recv_packet: select error :%d\n",errno);  
  173.                 break;  
  174.             default:  
  175.                 if(FD_ISSET(m_nSocketfd,&readfd)) //通过检测m_nSocketfd是否还在集合readfd中,判断m_nSocketfd是否有可读数据  
  176.                 {  
  177.                     //recevfrom接收数据同时获取数据发送者的源地址,保存在m_from_addr中  
  178.                     if((packetsize=recvfrom(m_nSocketfd,m_recvpacket,sizeof(m_recvpacket),0,(struct sockaddr *)&m_from_addr  
  179.                     ,(socklen_t*)&fromlen)) < 0)  
  180.                     {  
  181.                         printf("packetsize = %d\n",packetsize);  
  182.                         printf("recv_packet: recv error :%d\n",errno);   
  183.                         return ;  
  184.                     }  
  185.                     //保存最后一个接收到包的时间  
  186.                     gettimeofday(&m_tvrecv,NULL);  
  187.                     m_end_tvrecv.tv_usec = m_tvrecv.tv_usec;  
  188.                     m_end_tvrecv.tv_sec = m_tvrecv.tv_sec;  
  189.                     //拆包  
  190.                     if(unpack(m_recvpacket,packetsize) == -1)  
  191.                     {  
  192.                         continue;  
  193.                     }  
  194.                     m_nRecv++;  
  195.                 }//end if  
  196.                 break;  
  197.         }//end switch  
  198.     }//end whlie  
  199.       
  200. }//end send_packet  
  201.   
  202. //首先要构建一个ICMP数据包,对ICMP结构体成员变量赋值  
  203. int Cping::pack(int pack_number)  
  204. {  
  205.     int packsize;    
  206.     struct icmp *pIcmp;    
  207.     struct timeval *pTime;  
  208.       
  209.     pIcmp = (struct icmp*)m_sendpacket;    
  210.         
  211.     //类型和代码分别为ICMP_ECHO,0代表请求回送    
  212.     pIcmp->icmp_type = ICMP_ECHO;                    //ICMP报文类型,type和code共同决定  
  213.     pIcmp->icmp_code = 0;    
  214.     pIcmp->icmp_cksum = 0;                           //校验和    
  215.     pIcmp->icmp_seq = pack_number;                   //包序列号    
  216.     pIcmp->icmp_id = getpid();                       //取进程号作为标志  
  217.     packsize = 8 + SEND_DATA_LEN;                    //ICMP报文的大小包括8个字节的报头,和报文数据  
  218.     pTime = (struct timeval *)pIcmp->icmp_data;      //数据段存放发送时间    
  219.     gettimeofday(pTime, NULL);  
  220.     if(m_nSend == 0)                                 //第一次发送报文的时间  
  221.     {  
  222.         m_begin_tvrecv.tv_usec = pTime->tv_usec;  
  223.         m_begin_tvrecv.tv_sec = pTime->tv_sec;  
  224.     }  
  225.     <span style="white-space:pre">  </span>pIcmp->icmp_cksum = cal_chksum((unsigned short *)pIcmp,packsize);  
  226.       
  227.     return packsize;                                //返回ICMP报文大小  
  228. }  
  229.   
  230. //获取到IP数据包以后,要去除IP报头,完成ICMP报文的校验以及时间的提取  
  231. int Cping::unpack(char *buf, int len)  
  232. {  
  233.     int i,iphdrlen;   
  234.     struct icmp *pIcmp;    
  235.     struct timeval *tvsend;  
  236.     struct ip* recv_ip = (struct ip*)buf;  
  237.     double rtt;  
  238.       
  239.     iphdrlen = recv_ip->ip_hl << 2;                    //求ip报头长度,即ip报头的长度标志乘4    
  240.     pIcmp = (struct icmp*)(buf + iphdrlen);         //pIcmp指向的是ICMP头部,因此要跳过IP头部数据  
  241.     len -= iphdrlen;                        //ICMP报头及ICMP数据报的总长度  
  242.     if (len < 8) //小于ICMP报头长度则不合理  
  243.     {    
  244.        printf( "ICMP packets\'s length is less than 8");    
  245.           
  246.         return -1;   
  247.     }  
  248.     //对ICMP报文进行数据校验,提取时间  
  249.     if((pIcmp->icmp_type == ICMP_ECHOREPLY) && (m_copy_Ip == inet_ntoa(m_from_addr.sin_addr)) && (pIcmp->icmp_id = getpid()) )  
  250.     {  
  251.         tvsend = (struct timeval *)pIcmp->icmp_data;  
  252.   
  253.         tv_sub(&m_tvrecv,tvsend);  
  254.   
  255.         rtt = m_tvrecv.tv_sec * 1000 + (double)m_tvrecv.tv_usec / 1000; //以毫秒为单位计算rtt    
  256.    
  257.         //打印报文相关信息    
  258.        printf("%d byte from %s : icmp_seq=%u ttl=%d time=%.3fms\n",    
  259.                 len,    
  260.                 inet_ntoa(m_from_addr.sin_addr),    
  261.                 pIcmp->icmp_seq,    
  262.                 recv_ip->ip_ttl,    
  263.                 rtt);  
  264.     }  
  265.     else  
  266.     {  
  267.         printf("throw away the old package %d\tbyte from %s\t: icmp_seq=%u\tttl=%d\trtt=%.3f\tms",    
  268.         len,    
  269.         inet_ntoa(m_from_addr.sin_addr),    
  270.         pIcmp->icmp_seq,    
  271.         recv_ip->ip_ttl,    
  272.         rtt);    
  273.     
  274.         return -1;  
  275.     }  
  276.     return 1;  
  277. }  
  278.   
  279.   
  280. //时间的计算以及信息打印  
  281. void Cping::tv_sub(struct timeval *out, struct timeval *in)  
  282. {  
  283.     if((out->tv_usec -= in->tv_usec) < 0)  
  284.     {  
  285.         --out->tv_sec;  
  286.         out->tv_usec += 10000000;  
  287.     }  
  288.     out->tv_sec -= in->tv_sec;  
  289.       
  290. }  
  291. //打印收发信息  
  292. void Cping::statistics(int sig)  
  293. {     
  294.     tv_sub(&m_end_tvrecv,&m_begin_tvrecv);  
  295.     m_dTotalResponseTimes = m_end_tvrecv.tv_sec * 1000 + (double)m_end_tvrecv.tv_usec / 1000;  
  296.     printf("------statistics------\n");    
  297.     printf("%d packets transmitted, %d received , %d%% lost,time:%.3lfms\n", m_nSend, m_nRecv,    
  298.             (m_nSend - m_nRecv) / m_nSend * 100,m_dTotalResponseTimes);    
  299.     close(m_nSocketfd);    
  300. }  
  301.   
  302.   
  303. //ICMP校验和计算函数,这个校验和函数是通用的  
  304. unsigned short Cping::cal_chksum(unsigned short *addr, int len)  
  305. {  
  306.       
  307.     int nleft=len;    
  308.     int sum=0;    
  309.     unsigned short *w=addr;    
  310.     unsigned short answer=0;    
  311.     //采用32bit加法  
  312.     while(nleft > 1)    
  313.     {    
  314.         sum += *w++;    
  315.         nleft -= 2;    
  316.     }    
  317.     
  318.     if( nleft == 1)    
  319.     {    
  320.         *(unsigned char *)(&answer) = *(unsigned char *)w;    
  321.         sum += answer;    
  322.     }    
  323.     //把高16位加到低16位上去  
  324.     sum = (sum >> 16) + (sum & 0xffff);    
  325.     sum += (sum >> 16);    
  326.     answer = ~sum;    
  327.     
  328.     return answer;  
  329.       
  330. }  

四、在我的虚拟机上运行效果



  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值