linux c ping 实现

用c语言实现的linux中的ping命令

  1 #include <stdio.h>
  2 #include <signal.h>
  3 #include <arpa/inet.h>
  4 #include <pthread.h>
  5 #include <sys/types.h>
  6 #include <sys/socket.h>
  7 #include <unistd.h>
  8 #include <netinet/in.h>
  9 #include <netinet/ip.h>
 10 #include <netinet/ip_icmp.h>
 11 #include <netdb.h>
 12 #include <setjmp.h>
 13 #include <errno.h>
 14 #define PACKET_SIZE     4096
 15 #define MAX_WAIT_TIME   5
 16 #define MAX_NO_PACKETS  3
 17 char sendpacket[PACKET_SIZE];
 18 char recvpacket[PACKET_SIZE];
 19 int sockfd,datalen=56;
 20 int nsend=0,nreceived=0;
 21 struct sockaddr_in dest_addr;
 22 pid_t pid;
 23 struct sockaddr_in from;
 24 struct timeval tvrecv;
 25 pthread_t tid;
 26 void statistics();
 27 unsigned short cal_chksum(unsigned short *addr,int len);
 28 int pack(int pack_no);
 29 void send_packet(void);
 30 void recv_packet(void );
 31 int unpack(char *buf,int len);
 32 void tv_sub(struct timeval *out,struct timeval *in);
 33 void statistics()
 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         nsend++;
 83         packetsize=pack(nsend); /*设置ICMP报头*/
 84         if( sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr) )<0  )
 85         {      
 86         perror("sendto error");
 87                 return;
 88         }
 89     alarm(1);
 90         
 91 }
 92 /*接收所有ICMP报文*/
 93 void recv_packet(void )
 94 {       
 95     //int send = *(int *) arg;
 96     int n,fromlen;
 97         extern int errno;
 98         fromlen=sizeof(from);
 99     while(1)
100         {    if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0, (struct sockaddr *)&from,&fromlen)) <0)
101             {       
102             if(errno==EINTR)
103                     {
104                 perror("recvfrom error");
105             }
106             continue;
107         
108             }
109             gettimeofday(&tvrecv,NULL);  /*记录接收时间*/
110         unpack(recvpacket,n);
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         {       perror("socket error");
161                 exit(1);
162         }
163         /* 回收root权限,设置当前用户权限*/
164         //setuid(getuid());
165         /*扩大套接字接收缓冲区到50K这样做主要为了减小接收缓冲区溢出的
166           的可能性,若无意中ping一个广播地址或多播地址,将会引来大量应答*/
167         
168     setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
169         bzero(&dest_addr,sizeof(dest_addr));
170         dest_addr.sin_family=AF_INET;
171         /*判断是主机名还是ip地址*/
172         if( inaddr=inet_addr(argv[1])==INADDR_NONE)
173         {       if((host=gethostbyname(argv[1]) )==NULL) /*是主机名*/
174                 {       perror("gethostbyname error");
175                         exit(1);
176                 }
177                 memcpy( (char *)&dest_addr.sin_addr,host->h_addr,host->h_length);
178         }
179         else    /*是ip地址*/
180                 memcpy( (char *)&dest_addr,(char *)&inaddr,host->h_length);
181         /*获取main的进程id,用于设置ICMP的标志符*/
182         pid=getpid();
183         printf("PING %s(%s): %d bytes data in ICMP packets.\n",argv[1],
184                         inet_ntoa(dest_addr.sin_addr),datalen);
185 
186         signal(SIGALRM,send_packet);
187     signal(SIGINT,statistics);
188         send_packet();  /*发送所有ICMP报文*/
189         //recv_packet();  /*接收所有ICMP报文*/
190     int ret = pthread_create(&tid, NULL, (void*)recv_packet, NULL);
191     if(ret != 0)
192     {
193         printf("Thread Create Error\n");
194         exit(0);
195     }    
196         //statistics(SIGALRM); /*进行统计*/
197     pthread_join(tid, NULL);
198         return 0;
199 }
200 /*两个timeval结构相减*/
201 void tv_sub(struct timeval *out,struct timeval *in)
202 {       if( (out->tv_usec-=in->tv_usec)<0)
203         {       --out->tv_sec;
204                 out->tv_usec+=1000000;
205         }
206         out->tv_sec-=in->tv_sec;
207 }

 

 

 涉及到的技术线程,信号量。

在使用线程的时候注意pthread_join的使用,该函数类似于waitpid 。由于不知道哪个进程先结束,如果主线程先结束了,那么有些变量多个线程都在使用,有可能由于主线程的结束使一些资源(该例子中的socket),致使其他线程无法使用。

su root

gcc ping.c -o ping -lpthread

./ping www.baidu.com

 

posted on 2015-10-20 20:33  追寻前人的脚步  阅读(972)  评论(0编辑  收藏  举报

导航