Linux C 实现Ping功能的程序.

ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。ping命令的工作原理是:向网络上的另一个主机系统发送ICMP报文,如果指定系统得到了报文,它将把报文一模一样地传回给发送者,这有点象潜水艇声纳系统中使用的发声装置。

要真正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议。

ICMP(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。

代码贴上

 

 

  1 #include <stdio.h>
  2 #include <signal.h>
  3 #include <arpa/inet.h>
  4 #include <sys/types.h>
  5 #include <sys/socket.h>
  6 #include <unistd.h>
  7 #include <netinet/in.h>
  8 #include <netinet/ip.h>
  9 #include <netinet/ip_icmp.h>
 10 #include <netdb.h>
 11 #include <stdlib.h>
 12 #include <string.h>
 13 #include <setjmp.h>
 14 #include <errno.h>
 15 #define PACKET_SIZE     4096
 16 #define MAX_WAIT_TIME   5
 17 #define MAX_NO_PACKETS  3
 18 char sendpacket[PACKET_SIZE];
 19 char recvpacket[PACKET_SIZE];
 20 int sockfd,datalen=56;
 21 int nsend=0,nreceived=0;
 22 struct sockaddr_in dest_addr;//目标地址
 23 pid_t pid;
 24 struct sockaddr_in from;
 25 struct timeval tvrecv;
 26 void statistics(int signo);
 27 unsigned short cal_chksum(unsigned short *addr,int len);//校验算法
 28 int pack(int pack_no);//设置ICMP报头
 29 void send_packet(void);//发送报文
 30 void recv_packet(void);//接收报文
 31 int unpack(char *buf,int len);//剥去icmp报头
 32 void tv_sub(struct timeval *out,struct timeval *in);//时间结构
 33 void statistics(int signo)
 34 {       printf("\n--------------------PING statistics-------------------\n");
 35         printf("%d packets transmitted, %d received , %%%d lost\n",nsend,nreceived,
 36                         (nsend-nreceived)/nsend*100);
 37         close(sockfd);
 38         exit(1);
 39 }
 40 /*校验和算法*/
 41 unsigned short cal_chksum(unsigned short *addr,int len)
 42 {       int nleft=len;
 43         int sum=0;
 44         unsigned short *w=addr;
 45         unsigned short answer=0;
 46         
 47 /*把ICMP报头二进制数据以2字节为单位累加起来*/
 48         while(nleft>1)
 49         {       sum+=*w++;
 50                 nleft-=2;
 51         }
 52         /*若ICMP报头为奇数个字节,会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加*/
 53         if( nleft==1)
 54         {       *(unsigned char *)(&answer)=*(unsigned char *)w;
 55                 sum+=answer;
 56         }
 57         sum=(sum>>16)+(sum&0xffff);
 58         sum+=(sum>>16);
 59         answer=~sum;
 60         return answer;
 61 }
 62 /*设置ICMP报头*/
 63 int pack(int pack_no)
 64 {       int i,packsize;
 65         struct icmp *icmp;
 66         struct timeval *tval;
 67         icmp=(struct icmp*)sendpacket;
 68         icmp->icmp_type=ICMP_ECHO;
 69         icmp->icmp_code=0;
 70         icmp->icmp_cksum=0;
 71         icmp->icmp_seq=pack_no;
 72         icmp->icmp_id=pid;
 73         packsize=8+datalen;
 74         tval= (struct timeval *)icmp->icmp_data;
 75         gettimeofday(tval,NULL);    /*记录发送时间*/
 76         icmp->icmp_cksum=cal_chksum( (unsigned short *)icmp,packsize); /*校验算法*/
 77         return packsize;
 78 }
 79 /*发送三个ICMP报文*/
 80 void send_packet()
 81 {       int packetsize;
 82         while( nsend<MAX_NO_PACKETS)
 83         {       nsend++;
 84                 packetsize=pack(nsend); /*设置ICMP报头*/
 85                 
 86                     printf("%d",packetsize);
 87                 if( sendto(sockfd,sendpacket,packetsize,0,
 88                           (struct sockaddr *)&dest_addr,sizeof(dest_addr) )<0  )//(struct sockaddr*)&dest_addr 强转换&dest_addr(地址)为sockaddr指针
 89                 {       perror("sendto error");
 90                         continue;
 91                 }
 92                 sleep(1); /*每隔一秒发送一个ICMP报文*/
 93         }
 94 }
 95 /*接收所有ICMP报文*/
 96 void recv_packet()
 97 {       int n,fromlen;
 98         extern int errno;
 99         signal(SIGALRM,statistics);
100         fromlen=sizeof(from);
101         while( nreceived<nsend)
102         {       alarm(MAX_WAIT_TIME);
103                 if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,
104                                 (struct sockaddr *)&from,&fromlen)) <0)
105                 {       if(errno==EINTR)continue;
106                         perror("recvfrom error");
107                         continue;
108                 }
109                 gettimeofday(&tvrecv,NULL);  /*记录接收时间*/
110                 if(unpack(recvpacket,n)==-1)continue;
111                 nreceived++;
112         }
113 }
114 /*剥去ICMP报头*/
115 int unpack(char *buf,int len)
116 {       int i,iphdrlen;
117         struct ip *ip;
118         struct icmp *icmp;
119         struct timeval *tvsend;
120         double rtt;
121         ip=(struct ip *)buf;
122         iphdrlen=ip->ip_hl<<2;    /*求ip报头长度,即ip报头的长度标志乘4*/
123         icmp=(struct icmp *)(buf+iphdrlen);  /*越过ip报头,指向ICMP报头*/
124         len-=iphdrlen;            /*ICMP报头及ICMP数据报的总长度*/
125         if( len<8)                /*小于ICMP报头长度则不合理*/
126         {       printf("ICMP packets\'s length is less than 8\n");
127                 return -1;
128         }
129         /*确保所接收的是我所发的的ICMP的回应*/
130         if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id==pid) )
131         {       tvsend=(struct timeval *)icmp->icmp_data;
132                 tv_sub(&tvrecv,tvsend);  /*接收和发送的时间差*/
133                 rtt=tvrecv.tv_sec*1000+tvrecv.tv_usec/1000;  /*以毫秒为单位计算rtt*/
134                 /*显示相关信息*/
135                 printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms\n",
136                         len,
137                         inet_ntoa(from.sin_addr),
138                         icmp->icmp_seq,
139                         ip->ip_ttl,
140                         rtt);
141         }
142         else    return -1;
143 }
144 main(int argc,char *argv[])
145 {       struct hostent *host;
146         struct protoent *protocol;
147         unsigned long inaddr=0l;
148         int waittime=MAX_WAIT_TIME;
149         int size=50*1024;
150         if(argc<2)
151         {       printf("usage:%s hostname/IP address\n",argv[0]);
152                 exit(1);
153         }
154         if( (protocol=getprotobyname("icmp") )==NULL)
155         {       perror("getprotobyname");
156                 exit(1);
157         }
158         /*生成使用ICMP的原始套接字,这种套接字只有root才能生成*/
159         if( (sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto) )<0)
160         //getprotobyname()返回对应于给定协议名的包含名字和协议号的protoent结构指针
161         /*struct protoent {
162             char FAR *        p_name;
163             char Far * far *  p_aliases;
164             short             p_proto;
165             };*/
166         {       perror("socket error");
167                 exit(1);
168         }
169         /* 回收root权限,设置当前用户权限*/
170         setuid(getuid());
171         /*扩大套接字接收缓冲区到50K这样做主要为了减小接收缓冲区溢出的
172           的可能性,若无意中ping一个广播地址或多播地址,将会引来大量应答*/
173         setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
174         bzero(&dest_addr,sizeof(dest_addr));//初始化清零
175         dest_addr.sin_family=AF_INET;/*sin_family指代协议族,在socket编程中只能是AF_INET*/
176                             /*判断是主机名还是ip地址*/
177         if(INADDR_NONE == (inaddr=inet_addr(argv[1])))
178                              /*这里用inet_addr()参数判断是否为点格式IP 如果是其他格式则返回长整型数INADDR_NONE*/
179                             /* 这里如果返回INADDR_NONE说明是主机名*/
180         {       if((host=gethostbyname(argv[1]) )==NULL) //返回host主机名为空
181                 {       perror("gethostbyname error");
182                         exit(1);
183                 }
184                 memcpy( (char *)&dest_addr.sin_addr,host->h_addr,host->h_length);
185         }
186         else    /*是ip地址*/
187                 memcpy( (char *)&dest_addr,(char *)&inaddr,sizeof(dest_addr.sin_addr));
188         /*获取main的进程id,用于设置ICMP的标志符*/
189         pid=getpid();
190         printf("PING %s(%s): %d bytes data in ICMP packets.\n",argv[1],
191                         /*把网络地址转换为点格式的IP地址 */inet_ntoa(dest_addr.sin_addr),datalen);
192         send_packet();  /*发送所有ICMP报文*/
193         recv_packet();  /*接收所有ICMP报文*/
194         statistics(SIGALRM); /*进行统计*/
195         return 0;
196 }
197 /*两个timeval结构相减*/
198 void tv_sub(struct timeval *out,struct timeval *in)
199 {       if( (out->tv_usec-=in->tv_usec)<0)
200         {       --out->tv_sec;
201                 out->tv_usec+=1000000;
202         }
203         out->tv_sec-=in->tv_sec;
204 }

 

 

 

 

 

 

posted on 2013-07-26 15:28  IronMan_  阅读(1815)  评论(0编辑  收藏  举报