Linux C 實現Ping功能的程序.

ping命令是用來查看網絡上另外一個主機系統的網絡鏈接是否正常的一個工具。ping命令的工做原理是:向網絡上的另外一個主機系統發送ICMP報文,若是指定系統獲得了報文,它將把報文如出一轍地傳回給發送者,這有點象潛水艇聲納系統中使用的發聲裝置。web

要真正瞭解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 }
相關文章
相關標籤/搜索