ping的实现和代码分析
一.介紹 ? ??
ping命令是用來查看網絡上另一個主機系統的網絡連接是否正常的一個工具。ping命令的工作原理是:
向網絡上的另一個主機系統發送ICMP報文,如果指定系統得到了報文,它將把報文一模一樣地傳回給發送者,這有點象潛水艇聲納系統中使用的發聲裝置。 例如,在Linux終端上執行ping如下:
二.分析
由上面的執行結果可以看到,ping命令執行后顯示出被測試系統主機名和相應IP地址、返回給當前主機的ICMP報文順序號、ttl生存時間和往返時間rtt(單位是毫秒,即千分之一秒)。要寫一個模擬ping命令,這些信息有啟示作用。要真正了解ping命令實現原理,就要了解ping命令所使用到的TCP/IP協議。 ICMP(Internet Control Message,網際控制報文協議)是為網關和目標主機而提供的一種差錯控制機制,使它們在遇到差錯時能把錯誤報告給報文源發方。ICMP協議是IP層的一個協議,但是由于差錯報告在發送給報文源發方時可能也要經過若干子網,因此牽涉到路由選擇等問題,所以ICMP報文需通過IP協議來發送。ICMP數據報的數據發送前需要兩級封裝:首先添加ICMP報頭形成ICMP報文,再添加IP報頭形成IP數據報。由于IP層協議是一種點對點的協議,而非端對端的協議,它提供無連接的數據報服務,沒有端口的概念,因此很少使用bind()和connect()函數,若有使用也只是用于設置IP地址。明白了工作原理,我們就可以寫我們自己的ping命令:myping:
代碼1:
/*?????*????名稱:?????myping? *?????程序應用:??ping命令是向目的主機發送ICMP報文,檢驗本地主機和遠程的目的主機是否連接? *??????*?? */?????/*ICMP必須使用原始套接字進行設計,要手動設置IP的頭部和ICMP的頭部并行校驗*/?? /***********主函數*********************************************? myping.c*/?? #include?<sys/socket.h>?? #include?<netinet/in.h>?? #include?<netinet/ip.h>?? #include?<netinet/ip_icmp.h>?? #include?<unistd.h>?? #include?<signal.h>?? #include?<arpa/inet.h>?? #include?<errno.h>?? #include?<sys/time.h>?? #include?<stdio.h>?? #include?<string.h>?/*?bzero?*/?? #include?<netdb.h>?? #include?<pthread.h>?? //保存發送包的狀態值?? typedef?struct?pingm_pakcet{??struct?timeval?tv_begin;?????//發送時間??struct?timeval?tv_end;???????//接收到的時間??short?seq;???????????????????//序列號??int?flag;??????????//1,表示已經發送但是沒有接收到回應,0,表示接收到回應?? }pingm_pakcet;?? static?pingm_pakcet?*icmp_findpacket(int?seq);?? static?unsigned?short?icmp_cksum(unsigned?char?*data,?int?len);?? static?struct?timeval?icmp_tvsub(struct?timeval?end,?struct?timeval?begin);?? static?void?icmp_statistics(void);?? static?void?icmp_pack(struct?icmp?*icmph,?int?seq,?struct?timeval?*tv,int?length);?? static?int?icmp_unpack(char?*buf,int?len);?? static?void?*icmp_recv(void?*argv);?? static?void?icmp_sigint(int?signo);?? static?void?icmp_usage();???? static?pingm_pakcet?pingpacket[128];?? #define?K?1024?? #define?BUFFERSIZE?72????????????????????????????//發送緩沖區的大小?? static?unsigned?char?send_buff[BUFFERSIZE];???????? static?unsigned?char?recv_buff[2*K];?????????????//防止接收溢出,設置大一些?? static?struct?sockaddr_in?dest;??????????????????//目的地址?? static?int?rawsock?=?0;??????????????????????????//發送和接收線程需要的socket描述符?? static?pid_t?pid;????????????????????????????????//進程PID?? static?int?alive?=?0;????????????????????????????//是否接收到退出信號?? static?short?packet_send?=?0;????????????????????//已經發送的數據包數量?? static?short?packet_recv?=?0;????????????????????//已經接收的數據包數量?? static?char?dest_str[80];????????????????????????//目的主機字符串?? static?struct?timeval?tv_begin,?tv_end,?tv_interval;??2.計算發送和接收的時間 static?void?icmp_usage()?? {??//ping加IP地址或者域名??printf("ping?aaa.bbb.ccc.ddd\n");?? }?? /*終端信號處理函數SIGINT*/?? static?void?icmp_sigint(int?signo)?? {??alive?=?0;??gettimeofday(&tv_end,NULL);??tv_interval?=?icmp_tvsub(tv_end,?tv_begin);????return;?? }?3.統計數據結果 /*統計數據結果函數******************************************? 打印全部ICMP發送的接收統計結果*/?? static?void?icmp_statistics(void)??{??long?time?=?(tv_interval.tv_sec?*?1000)?+?(tv_interval.tv_usec/1000);??printf("---?%s?ping?statistics?---\n",?dest_str);??printf("%d?packets?transmitted,?%d?received,?%d%c?packet?loss,?time?%ld?ms\n",???packet_send,packet_recv,(packet_send-packet_recv)*100/packet_send,'%',time);?? }??/*************查找數組中的標識函數***********************?查找合適的包的位置? 當seq為1時,表示查找空包? 其他值表示查找seq對應的包*/??static?pingm_pakcet?*icmp_findpacket(int?seq)??{??int?i;??pingm_pakcet?*found?=?NULL;??//查找包的位置??if(seq?==?-1){??for(i=0;i<128;i++){??if(pingpacket[i].flag?==?0){??found?=?&pingpacket[i];??break;??}??}??}??else?if(seq?>=?0){??for(i?=0?;i<?128;i++){??if(pingpacket[i].seq?==?seq){??found?=?&pingpacket[i];??break;??}??}??}??return?found;??}?4.校驗和函數 /*************校驗和函數*****************************? TCP/IP協議棧使用的校驗算法是比較經典的,對16位的數據進行累加計算,并返回計算結果,?CRC16校驗和計算icmp_cksum? 參數:?data:數據?len:數據長度? 返回值:?計算結果,short類型? */?? static?unsigned?short?icmp_cksum(unsigned?char?*data,?int?len)?? {??int?sum?=?0;???//計算結果??int?odd?=?len?&?0x01;??//是否為奇數??/*將數據按照2字節為單位累加起來*/??while(len?&?0xfffe){??sum?+=?*(unsigned?short*)data;??data?+=?2;??len?-=?2;??}??/*判斷是否為奇數個數據,若ICMP報頭為奇數個字節,會剩下最后一個字節*/??if(odd){??unsigned?short?tmp?=?((*data)<<8)&0xff00;??sum?+=?tmp;??}??sum?=?(sum?>>?16)?+?(sum?&?0xffff);???//高地位相加??sum?+=?(sum?>>?16);????????????????????//將溢出位加入??return?~sum;???????????????????????????//返回取反值?? }?5.ICMP頭部校驗打包和拆包 /**********進行ICMP頭部校驗********************/?? //設置ICMP報頭?? static?void?icmp_pack(struct?icmp?*icmph,?int?seq,?struct?timeval?*tv,?int?length)?? {??unsigned?char?i?=?0;??//設置報頭??icmph->icmp_type?=?ICMP_ECHO;???//ICMP回顯請求??icmph->icmp_code?=?0;???????????//code的值為0??icmph->icmp_cksum?=?0;??????????//先將cksum的值填為0,便于以后的cksum計算??icmph->icmp_seq?=?seq;??????????//本報的序列號??icmph->icmp_id?=?pid?&?0xffff;??//填寫PID??for(i=0;?i<?length;?i++)??icmph->icmp_data[i]?=?i;???//計算校驗和??icmph->icmp_cksum?=?icmp_cksum((unsigned?char*)icmph,?length);?? }??/*解壓接收到的包,并打印信息*/?? static?int?icmp_unpack(char?*buf,?int?len)?? {??int?i,iphdrlen;??struct?ip?*ip?=?NULL;??struct?icmp?*icmp?=?NULL;??int?rtt;??ip?=?(struct?ip?*)buf;????????????//IP報頭??iphdrlen?=?ip->ip_hl?*?4;?????????//IP頭部長度??icmp?=?(struct?icmp?*)(buf+iphdrlen);??//ICMP段的地址??len?-=?iphdrlen;??//判斷長度是否為ICMP包??if(len?<?8){??printf("ICMP?packets\'s?length?is?less?than?8\n");??return?-1;??}??//ICMP類型為ICMP_ECHOREPLY并且為本進程的PID??if((icmp->icmp_type?==?ICMP_ECHOREPLY)?&&?(icmp->icmp_id?==?pid)){??struct?timeval?tv_interval,tv_recv,tv_send;??//在發送表格中查找已經發送的包,按照seq??pingm_pakcet?*packet?=?icmp_findpacket(icmp->icmp_seq);??if(packet?==?NULL)??return?-1;??packet->flag?=?0;??????????//取消標志??tv_send?=?packet->tv_begin;??//獲取本包的發送時間??gettimeofday(&tv_recv,NULL);??//讀取此時間,計算時間差??tv_interval?=?icmp_tvsub(tv_recv,tv_send);??rtt?=?tv_interval.tv_sec?*?1000?+?tv_interval.tv_usec/1000;??/*打印結果包含?ICMP段的長度?源IP地址?包的序列號?TTL?時間差?*/??printf("%d?byte?from?%s:?icmp_seq=%u?ttl=%d?rtt=%d?ms\n",???len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt);??packet_recv?++;??????????????//接收包數量加1??}??else?{??return?-1;??}?? }6.計算時間差函數 /************計算時間差time_sub************************? 參數:?end:接收到時間?begin:開始發送的時間? 返回值:?使用的時間? */?? static?struct?timeval?icmp_tvsub(struct?timeval?end,?struct?timeval?begin)?? {??struct?timeval?tv;??//計算差值??tv.tv_sec?=?end.tv_sec?-?begin.tv_sec;??tv.tv_usec?=?end.tv_usec?-?begin.tv_usec;??//如果接收的時間的usec值小于發送時的usec,從uesc域借位??if(tv.tv_usec?<?0){??tv.tv_sec?--;??tv.tv_usec?+=?1000000;??}??return?tv;?? }?7.發送報文函數 //**********發送報文***************************?? static?void?*icmp_send(void?*argv)?? {??//保存程序開始發送數據的時間??gettimeofday(&tv_begin,?NULL);??while(alive){??int?size?=?0;??struct?timeval?tv;??gettimeofday(&tv,?NULL);?????//當前包的發送時間??//在發送包狀態數組中找到一個空閑位置??pingm_pakcet?*packet?=?icmp_findpacket(-1);??if(packet){??packet->seq?=?packet_send;??packet->flag?=?1;??gettimeofday(&packet->tv_begin,NULL);??}??icmp_pack((struct?icmp?*)send_buff,packet_send,&tv,?64);??//打包數據??size?=?sendto(rawsock,?send_buff,64,0,(struct?sockaddr?*)&dest,?sizeof(dest));??if(size?<?0){??perror("sendto?error");??continue;??}??packet_send?++;??//每隔1s發送一個ICMP回顯請求包??sleep(1);??}?? }8.接收目的主機的回復函數 /***********接收ping目的主機的回復***********?/? static?void?*icmp_recv(void?*argv)?? {??//輪詢等待時間??struct?timeval?tv;??tv.tv_usec?=?200;??tv.tv_sec?=?0;??fd_set?readfd;??//當沒有信號發出一直接收數據??while(alive){??int?ret?=?0;??FD_ZERO(&readfd);??FD_SET(rawsock,&readfd);??ret?=?select(rawsock+1,&readfd,NULL,NULL,&tv);??switch(ret)??{??case?-1:??//錯誤發生??break;??case?0:??//超時??break;??default?:??{??//收到一個包??int?fromlen?=?0;??struct?sockaddr?from;??//接收數據??int?size?=?recv(rawsock,recv_buff,sizeof(recv_buff),0);??if(errno?==?EINTR){??perror("recvfrom?error");??continue;??}??//解包??ret?=?icmp_unpack(recv_buff,size);??if(ret?==?1){??continue;??}??}??break;??}??}?? }?9.設置ICMP頭部(程序中不需要,這里只是作為了解) /**********設置ICMP發送報文的頭部*********************************?回顯請求的ICMP報文?*/?? /*struct?icmp? {?u_int8_t?icmp_type;???//消息類型?u_int8_t?icmp_code;???//消息類型的子碼?u_int16_t?icmp_cksum;???//校驗和?union?{?struct?ih_idseq????//顯示數據報?{?u_int16_t?icd_id;??//數據報ID?u_int16_t?icd_seq;??//數據報的序號?}ih_idseq;???}icmp_hun;? #define?icmp_id?icmp_hun.ih_idseq.icd_id;? #define?icmp_seq?icmp_hun.ih_idseq.icd_seq;?union?{?u_int8_t?id_data[1];????//數據?}icmp_dun;? #define?icmp_data?icmp_dun.id_data;? };?*/10.主函數 //主程序?? int?main(int?argc,?char?const?*argv[])?? {??struct?hostent?*host?=?NULL;??struct?protoent?*protocol?=?NULL;??char?protoname[]?=?"icmp";??unsigned?long?inaddr?=?1;??int?size?=?128*K;??if(argc?<?2)?????????????????????//參數是否數量正確??{??icmp_usage();??return?-1;??}??//獲取協議類型??protocol?=?getprotobyname(protoname);??if(protocol?==?NULL)??{??perror("getprotobyname()");??return?-1;??}??//復制目的地址字符串??memcpy(dest_str,?argv[1],strlen(argv[1])+1);??memset(pingpacket,?0,?sizeof(pingm_pakcet)?*?128);??//socket初始化??rawsock?=?socket(AF_INET,?SOCK_RAW,?protocol->p_proto);??if(rawsock?<?0){??perror("socket");??return?-1;??}??pid?=?getuid();???????????????????????//為與其他線程區別,加入pid??//增大接收緩沖區,防止接收包被覆蓋??setsockopt(rawsock,?SOL_SOCKET,?SO_RCVBUF,?&size,?sizeof(size));??bzero(&dest,?sizeof(dest));??//獲取目的地址的IP地址??dest.sin_family?=?AF_INET;??//輸入的目的地址為字符串IP地址??inaddr?=?inet_addr(argv[1]);??if(inaddr?==?INADDR_NONE){?????????????//輸入的是DNS地址??host?=?gethostbyname(argv[1]);??if(host?==?NULL){??perror("gethostbyname");??return?-1;??}??//將地址復制到dest??memcpy((char?*)&dest.sin_addr,?host->h_addr,?host->h_length);??}???????????????????????????????????????//IP地址字符串??else?{??memcpy((char?*)&dest.sin_addr,?&inaddr,sizeof(inaddr));??}??//打印提示??inaddr?=?dest.sin_addr.s_addr;??printf("PING?%s?(%ld.%ld.%ld.%ld)?56(84)?bytes?of?data.\n",???dest_str,(inaddr&0x000000ff)>>0,(inaddr&0x0000ff00)>>8,(inaddr&0x00ff0000)>>16,(inaddr&0xff000000)>>24);??//截取信號SIGINT,將icmp_sigint掛接上??signal(SIGINT,icmp_sigint);??/*發送數據并接收回應?建立兩個線程,一個用于發數據,另一個用于接收響應數據,主程序等待兩個線程運行完畢后再進行?下一步,最后對結果進行統計并打印?*/??alive?=?1;?????????????????????????????????????//初始化可運行??pthread_t?send_id,?recv_id;????????????????????//建立兩個線程,用于發送和接收??int?err?=?0;??err?=?pthread_create(&send_id,?NULL,?icmp_send,?NULL);?//發送??if(err?<??0){??return?-1;??}??err?=?pthread_create(&recv_id,?NULL,?icmp_recv,?NULL);?//接收??if(err?<?0){??return?-1;??}??//等待線程結束??pthread_join(send_id,?NULL);??pthread_join(recv_id,?NULL);??//清理并打印統計結果??close(rawsock);??icmp_statistics();??return?0;?? }?三.程序運行方法由于在程序中用到了<pthread.h>多線程,所以在編譯時要加上-lpthread,并且想要運行的話要在root權限下,一般的用戶是沒有權限的此程序編譯方法為:
gcc -o myping myping.c -lpthread?
./myping www.baidu.com
結果為:
代碼2:
#include "stdafx.h" #include<stdio.h> #include<windows.h> #include<process.h>#pragma comment( lib, "ws2_32.lib" )#define SEND_SIZE 32 #define PACKET_SIZE 4096 #define ICMP_ECHO 8 #define ICMP_ECHOREPLY 0struct icmp {unsigned char icmp_type;unsigned char icmp_code;unsigned short icmp_cksum;unsigned short icmp_id;unsigned short icmp_seq;unsigned long icmp_data; };struct ip {unsigned char ip_hl:4; unsigned char ip_v:4; unsigned char ip_tos; unsigned short ip_len; unsigned short ip_id; unsigned short ip_off; unsigned char ip_ttl; unsigned char ip_p; unsigned short ip_sum; unsigned long ip_src;unsigned long ip_dst; };/*unsigned */char sendpacket[PACKET_SIZE]; /*unsigned */char recvpacket[PACKET_SIZE]; struct sockaddr_in dest_addr; struct sockaddr_in from_addr; int sockfd; int pid;unsigned short cal_chksum(unsigned short *addr,int len); int pack(int pack_no); int unpack(/*unsigned*/ char *buf,int len); void send_packet(void); void recv_packet(void);void main(int argc,char *argv[]) { struct hostent *host;struct protoent *protocol;WSADATA wsaData;int timeout=1000;int SEND_COUNT=4;int i;char* par_host = "www.baidu.com";/*par_host=argv[argc-1];switch(argc){case 2: break;case 3: if(strcmp(argv[1],"-t")==0){SEND_COUNT=10000;break;}//fall throughdefault:printf("usage: %s [-t] Host name or IP address\n",argv[0]);exit(1); }*/if(WSAStartup(0x1010,&wsaData)!=0){printf("wsastartup error\n");exit(1);}if( (protocol=getprotobyname("icmp") )==NULL){printf("getprotobyname error\n");exit(1);}if( (sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto) )<0){ printf("socket error\n");exit(1);}if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout))<0) fprintf(stderr,"failed to set recv timeout: %d\n",WSAGetLastError());if(setsockopt(sockfd,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout))<0)fprintf(stderr,"failed to set send timeout: %d\n",WSAGetLastError()); memset(&dest_addr,0,sizeof(dest_addr));dest_addr.sin_family=AF_INET;if(host=gethostbyname(par_host) ){memcpy( (char *)&dest_addr.sin_addr,host->h_addr,host->h_length);//resolve address to hostnameif(host=gethostbyaddr(host->h_addr,4,PF_INET))par_host=host->h_name;}else if( dest_addr.sin_addr.s_addr=inet_addr(par_host)==INADDR_NONE){printf("Unkown host %s\n",par_host);exit(1);}pid = _getpid();printf("Pinging %s [%s]: with %d bytes of data:\n\n",par_host,inet_ntoa(dest_addr.sin_addr),SEND_SIZE); for(i=0;i<SEND_COUNT;i++) {send_packet();recv_packet();Sleep(1000);} }//this algorithm is referenced from other's unsigned short cal_chksum(unsigned short *addr,int len)d //打包 int pack(int pack_no) { int packsize;struct icmp *icmp;packsize=8+SEND_SIZE;icmp=(struct icmp*)sendpacket;icmp->icmp_type=ICMP_ECHO;icmp->icmp_code=0;icmp->icmp_cksum=0;icmp->icmp_seq=pack_no;icmp->icmp_id=pid;icmp->icmp_data=GetTickCount();icmp->icmp_cksum=cal_chksum( (unsigned short *)icmp,packsize); /*校驗算法*/return packsize; }//解包 int unpack(/*unsigned*/ char *buf,int len) { struct ip *ip;struct icmp *icmp;double rtt;int iphdrlen;ip=(struct ip *)buf;iphdrlen=ip->ip_hl*4; icmp=(struct icmp *)(buf+iphdrlen); if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id==pid) ){len=len-iphdrlen-8; rtt=GetTickCount()-icmp->icmp_data; printf("Reply from %s: bytes=%d time=%.0fms TTL=%d icmp_seq=%u\n", inet_ntoa(from_addr.sin_addr),len,rtt,ip->ip_ttl,icmp->icmp_seq);return 1;}return 0; }//發送 void send_packet() { int packetsize;static int pack_no=0;packetsize=pack(pack_no++);if( sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr) )<0 )printf("Destination host unreachable.\n");// printf("send NO %d\n",pack_no-1); }//接收 void recv_packet() { int n,fromlen;int success;fromlen=sizeof(from_addr);do{if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr *)&from_addr,&fromlen)) >=0)success=unpack(recvpacket,n);else if (WSAGetLastError() == WSAETIMEDOUT){printf("Request timed out.\n");return;}}while(!success);}gcc -o myping myping.c -lpthread ./myping www.baidu.com
gcc -o myping myping.c -lpthread ./myping www.baidu.com
總結
以上是生活随笔為你收集整理的ping的实现和代码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QQ采用什么传输协议?
- 下一篇: 嵌入式处理器的体系架构与内核详解