linux下ping编程

本文深入解析了Ping命令的工作原理,从TCP/IP协议的角度出发,详细介绍了Ping如何利用ICMP报文进行主机间通信,以及如何测量往返时间等关键信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先来说说ping程序的原理吧,其实挺简单,就是一个主机系统向另外一个主机系统说:I love you(ICMP报文),然后那个主机如果相信你或者说想和你通信,和你心知心,那它就把收到的I love you(ICMP)报文原样返回.好嘛,源主机收到这个回应后,就happy了,因为对方是和自己心连心的。如果对方没有收到这个消息,或者对你不感冒,不愿意理你,不回你这个报文,或者说些不知云是云雾是雾的话,对不起啦,感情是两个人的事情哦.

     要想深刻了解,需有入目三分的实力,这个ping也一样,咱们先来看看它采用的TCP/IP协议,我刚说了,它发送的是ICMP回显请求,回答的是回显应答报文。谈起这个ICMP(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方.是IP层的一个协议。但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。如下图所示:

IP报头
ICMP报头
ICMP数据报

     由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect() 函数,若有使用也只是用于设置IP地址。发送数据使用sendto()函数,接收数据使用recvfrom()函数。

     TCP/IP的经典大作《TCP/IP协议详解.卷一》清晰的告诉我,IP报头格式如下:

    IP报头格式

    详细的,小王那懒的人都知道翻翻上面提到的书,我也就不详细介绍了,我这里给出linux中的数据结构实现:

[cpp]  view plain copy
 
  1. <span style="font-size:18px;color:#3333ff;">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.   };  
  24. </span>  


别看这多,其实ping程序用到的没几个:

    (1)IP报头长度IHL(Internet Header Length)以4字节为一个单位来记录IP报头的长度,是上述IP数据结构的ip_hl变量。

    (2)生存时间TTL(Time To Live)以秒为单位,指出IP数据报能在网络上停留的最长时间,其值由发送方设定,并在经过路由的每一个节点时减一,当该值为0时,数据报将被丢弃,是上述IP数据结构的ip_ttl变量。ICMP报文分为两种:查询报文和差错报文。每个ICMP报头均包含类型、编码和校验和这三项内容,其余选项则随ICMP的功能不同而不同。ICMP报文格式如下:

    ICMP

    Ping命令只使用众多ICMP报文中的两种:"请求回送'(ICMP_ECHO)和"请求回应'(ICMP_ECHOREPLY)。在Linux中定义如下:

 

#define ICMP_ECHO   0 #define ICMP_ECHOREPLY  8

 

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

 

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

    Ping命令中需要显示的信息,包括icmp_seq和ttl都已有实现的办法,但还缺rtt往返时间。为了实现这一功能,可利用ICMP数据报携带一个时间戳。使用以下函数生成时间戳:

 

 
  1. #include  
  2. int gettimeofday(struct timeval *tp,void *tzp) 
  3. 其中timeval结构如下: 
  4.   struct timeval{ 
  5.    long tv_sec; 
  6.    long tv_usec; 

 

    在发送和接收报文时由gettimeofday分别生成两个timeval结构,两者之差即为往返时间,即 ICMP报文发送与接收的时间差,而timeval结构由ICMP数据报携带,

tzp指针表示时区,一般都不使用,赋NULL值。系统自带的ping命令当它接送完所有ICMP报文后,会对所有发送和所有接收的ICMP报文进行统计,从而计算ICMP报文丢失的比率。为达此目的,定义两个全局变量:接收计数器和发送计数器,用于记录ICMP报文接受和发送数目。丢失数目=发送总数-接收总数,丢失比率=丢失数目/发送总数。现给出模拟Ping程序功能的代码如下:

 

 
  1. /******************************************************** 
  2.  *  IP报头格式数据结构定义在<netinet/ip.h>中    * 
  3.  *  ICMP数据结构定义在<netinet/ip_icmp.h>中     * 
  4.  *  套接字地址数据结构定义在<netinet/in.h>中 * 
  5.  ********************************************************/ 
  6.  
  7. #include <stdio.h> 
  8. #include <stdlib.h> 
  9. #include <signal.h> 
  10. #include <arpa/inet.h> 
  11. #include <sys/types.h> 
  12. #include <sys/socket.h> 
  13. #include <unistd.h> 
  14. #include <netinet/in.h> 
  15. #include <netinet/ip.h> 
  16. #include <netinet/ip_icmp.h> 
  17. #include <netdb.h> 
  18. #include <setjmp.h> 
  19. #include <errno.h> 
  20.  
  21. #define PACKET_SIZE 4096 
  22. #define MAX_WAIT_TIME   5 
  23. #define MAX_NO_PACKETS  10000 
  24.  
  25.  
  26. char *addr[]; 
  27. char sendpacket[PACKET_SIZE]; 
  28. char recvpacket[PACKET_SIZE]; 
  29. int sockfd,datalen = 56
  30. int nsend = 0nreceived = 0
  31. double temp_rtt[MAX_NO_PACKETS]; 
  32. double all_time = 0
  33. double min = 0
  34. double max = 0
  35. double avg = 0
  36. double mdev = 0
  37.  
  38. struct sockaddr_in dest_addr; 
  39. struct sockaddr_in from; 
  40. struct timeval tvrecv; 
  41. pid_t pid; 
  42.  
  43. void statistics(int sig); 
  44. void send_packet(void); 
  45. void recv_packet(void); 
  46. void computer_rtt(void); 
  47. void tv_sub(struct timeval *out,struct timeval *in); 
  48. int pack(int pack_no); 
  49. int unpack(char *buf,int len); 
  50. unsigned short cal_checksum(unsigned short *addr,int len); 
  51.  
  52. /*计算rtt最小、大值,平均值,算术平均数差*/ 
  53. void computer_rtt() 
  54.     double sum_avg = 0
  55.     int i; 
  56.     min = max = temp_rtt[0]; 
  57.     avg = all_time/nreceived; 
  58.  
  59.     for(i=0; i<nreceived; i++){ 
  60.         if(temp_rtt[i] < min
  61.             min = temp_rtt[i]; 
  62.         else if(temp_rtt[i] > max) 
  63.             max = temp_rtt[i]; 
  64.  
  65.         if((temp_rtt[i]-avg) < 0
  66.             sum_avg += avg - temp_rtt[i]; 
  67.         else 
  68.             sum_avg += temp_rtt[i] - avg;  
  69.         } 
  70.     mdev = sum_avg/nreceived; 
  71.  
  72. /****统计数据函数****/ 
  73. void statistics(int sig) 
  74.     computer_rtt();     //计算rtt 
  75.     printf("\n------ %s ping statistics ------\n",addr[0]); 
  76.     printf("%d packets transmitted,%d received,%d%% packet loss,time %.f ms\n", 
  77.         nsend,nreceived,(nsend-nreceived)/nsend*100,all_time); 
  78.     printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n", 
  79.         min,avg,max,mdev); 
  80.     close(sockfd); 
  81.     exit(1); 
  82.  
  83. /****检验和算法****/ 
  84. unsigned short cal_chksum(unsigned short *addr,int len) 
  85.     int nleft = len
  86.     int sum = 0
  87.     unsigned short *w = addr
  88.     unsigned short check_sum = 0
  89.  
  90.     while(nleft>1)      //ICMP包头以字(2字节)为单位累加 
  91.     { 
  92.         sum += *w++; 
  93.         nleft -2
  94.     } 
  95.  
  96.     if(nleft == 1)      //ICMP为奇数字节时,转换最后一个字节,继续累加 
  97.     { 
  98.         *(unsigned char *)(&check_sum) = *(unsigned char *)w; 
  99.         sum += check_sum; 
  100.     } 
  101.     sum = (sum >> 16) + (sum & 0xFFFF); 
  102.     sum += (sum >> 16); 
  103.     check_sum = ~sum;   //取反得到校验和 
  104.     return check_sum; 
  105.  
  106. /*设置ICMP报头*/ 
  107. int pack(int pack_no) 
  108.     int i,packsize; 
  109.     struct icmp *icmp; 
  110.     struct timeval *tval; 
  111.     icmp = (struct icmp*)sendpacket; 
  112.     icmp->icmp_type = ICMP_ECHO;    //ICMP_ECHO类型的类型号为0 
  113.     icmp->icmp_code = 0
  114.     icmp->icmp_cksum = 0
  115.     icmp->icmp_seq = pack_no;   //发送的数据报编号 
  116.     icmp->icmp_id = pid
  117.  
  118.     packsize = 8 + datalen;     //数据报大小为64字节 
  119.     tval = (struct timeval *)icmp->icmp_data; 
  120.     gettimeofday(tval,NULL);        //记录发送时间 
  121.     //校验算法 
  122.     icmp->icmp_cksum =  cal_chksum((unsigned short *)icmp,packsize);     
  123.     return packsize; 
  124.  
  125. /****发送三个ICMP报文****/ 
  126. void send_packet() 
  127.     int packetsize; 
  128.     if(nsend < MAX_NO_PACKETS
  129.     { 
  130.         nsend++; 
  131.         packpacketsize = pack(nsend);   //设置ICMP报头 
  132.         //发送数据报 
  133.         if(sendto(sockfd,sendpacket,packetsize,0, 
  134.             (struct sockaddr *)&dest_addr,sizeof(dest_addr)) < 0
  135.         { 
  136.             perror("sendto error"); 
  137.         } 
  138.     } 
  139.  
  140.  
  141.  
  142. /****接受所有ICMP报文****/ 
  143. void recv_packet() 
  144.     int n,fromlen; 
  145.     extern int error; 
  146.     fromlen = sizeof(from); 
  147.     if(nreceived < nsend
  148.     {    
  149.         //接收数据报 
  150.         if((n = recvfrom(sockfd,recvpacket,sizeof(recvpacket),0, 
  151.             (struct sockaddr *)&from,&fromlen)) < 0
  152.         { 
  153.             perror("recvfrom error"); 
  154.         } 
  155.         gettimeofday(&tvrecv,NULL);     //记录接收时间 
  156.         unpack(recvpacket,n);       //剥去ICMP报头 
  157.         nreceived++; 
  158.     } 
  159.  
  160.  
  161. /******剥去ICMP报头******/ 
  162. int unpack(char *buf,int len) 
  163.     int i; 
  164.     int iphdrlen;       //ip头长度 
  165.     struct ip *ip; 
  166.     struct icmp *icmp; 
  167.     struct timeval *tvsend; 
  168.     double rtt; 
  169.  
  170.  
  171.     ip = (struct ip *)buf; 
  172.     ipiphdrlen = ip->ip_hl << 2;  //求IP报文头长度,即IP报头长度乘4 
  173.     icmp = (struct icmp *)(buf + iphdrlen); //越过IP头,指向ICMP报头 
  174.     len -iphdrlen;    //ICMP报头及数据报的总长度 
  175.     if(len < 8)     //小于ICMP报头的长度则不合理 
  176.     { 
  177.         printf("ICMP packet\'s length is less than 8\n"); 
  178.         return -1; 
  179.     } 
  180.     //确保所接收的是所发的ICMP的回应 
  181.     if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)) 
  182.     { 
  183.         tvsend = (struct timeval *)icmp->icmp_data; 
  184.         tv_sub(&tvrecv,tvsend); //接收和发送的时间差 
  185.         //以毫秒为单位计算rtt 
  186.         rtt = tvrecv.tv_sec*1000 + tvrecv.tv_usec/1000; 
  187.         temp_rtt[nreceived] = rtt; 
  188.         all_time += rtt;    //总时间 
  189.         //显示相关的信息 
  190.         printf("%d bytes from %s: icmp_seq=%u ttl=%d time=%.1f ms\n", 
  191.                 len,inet_ntoa(from.sin_addr), 
  192.                 icmp->icmp_seq,ip->ip_ttl,rtt); 
  193.     } 
  194.     else return -1; 
  195.  
  196.  
  197. //两个timeval相减 
  198. void tv_sub(struct timeval *recvtime,struct timeval *sendtime) 
  199.     long sec = recvtime->tv_sec - sendtime->tv_sec; 
  200.     long usec = recvtime->tv_usec - sendtime->tv_usec; 
  201.     if(usec >= 0){ 
  202.         recvtime->tv_sec = sec; 
  203.         recvtime->tv_usec = usec; 
  204.     }else{ 
  205.         recvtime->tv_sec = sec - 1; 
  206.         recvtime->tv_usec = -usec; 
  207.     } 
  208.  
  209. /*主函数*/ 
  210. main(int argc,char *argv[]) 
  211.     struct hostent *host; 
  212.     struct protoent *protocol; 
  213.     unsigned long inaddr = 0
  214. //  int waittime = MAX_WAIT_TIME
  215.     int size = 50 * 1024; 
  216.     addr[0] = argv[1]; 
  217.     //参数小于两个 
  218.     if(argc < 2)         
  219.     { 
  220.         printf("usage:%s hostname/IP address\n",argv[0]); 
  221.         exit(1); 
  222.     } 
  223.     //不是ICMP协议 
  224.     if((protocol = getprotobyname("icmp")) == NULL) 
  225.     { 
  226.         perror("getprotobyname"); 
  227.         exit(1); 
  228.     } 
  229.  
  230.     //生成使用ICMP的原始套接字,只有root才能生成 
  231.     if((sockfd = socket(AF_INET,SOCK_RAW,protocol->p_proto)) < 0
  232.     { 
  233.         perror("socket error"); 
  234.         exit(1); 
  235.     } 
  236.  
  237.     //回收root权限,设置当前权限 
  238.     setuid(getuid()); 
  239.  
  240.     /*扩大套接字的接收缓存区导50K,这样做是为了减小接收缓存区溢出的 
  241.       可能性,若无意中ping一个广播地址或多播地址,将会引来大量的应答*/ 
  242.     setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size)); 
  243.     bzero(&dest_addr,sizeof(dest_addr));    //初始化 
  244.     dest_addr.sin_family = AF_INET;     //套接字域是AF_INET(网络套接字) 
  245.  
  246.     //判断主机名是否是IP地址 
  247.     if(inet_addr(argv[1]) == INADDR_NONE) 
  248.     { 
  249.         if((host = gethostbyname(argv[1])) == NULL) //是主机名 
  250.         { 
  251.             perror("gethostbyname error"); 
  252.             exit(1); 
  253.         } 
  254.         memcpy((char *)&dest_addr.sin_addr,host->h_addr,host->h_length); 
  255.     } 
  256.     else{ //是IP 地址 
  257.         dest_addr.sin_addr.s_addr = inet_addr(argv[1]); 
  258.     } 
  259.     pid = getpid(); 
  260.     printf("PING %s(%s):%d bytes of data.\n",argv[1], 
  261.             inet_ntoa(dest_addr.sin_addr),datalen); 
  262.  
  263.     //当按下ctrl+c时发出中断信号,并开始执行统计函数 
  264.     signal(SIGINT,statistics);   
  265.     while(nsend < MAX_NO_PACKETS){ 
  266.         sleep(1);       //每隔一秒发送一个ICMP报文 
  267.         send_packet();      //发送ICMP报文 
  268.         recv_packet();      //接收ICMP报文 
  269.     } 
  270.     return 0; 

 



本文出自 “dong4716138” 博客,请务必保留此出处http://dong4716138.blog.51cto.com/5136637/1193984

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值