您好,欢迎访问三七文档
Ping程序的实现Ping命令主要用来向目的主机发送ICMPECHO_REQUEST请求并接收目的主机返回的响应报文,用来检测本地主机和远程的主机是否连接。ICMP报文格式如下:类型代表发送报文的类型,如ping命令发送请求报文类型为8,代码一般为0,检验和为检验数据的传输的正确性,标识符一般为从0开始每次递增1的一组数据,序号通常由进程PID填充。本文中简化ICMP报文结构如下:Structicmp{u_int8_ticmp_type;//消息类型u_int8_ticmp_code;//消息类型的子码u_int16_ticmp_cksum;//校验和union{Structin_idseq{u_int16_ticd_id;//数据报idu_int16_ticd_seq;//数据报序号}in_idseq;}icmp_hun;#defineicmp_idicmp_hun.in_idseq.icd_id;#defineicmp_seqicmp_hun.in_idseq.icd_seq;union{u_int8_tid_data[1];}icmp_dun;//数据#defineicmp_dataicmp_dun.id_data};即包含了消息类型、消息代码、校验和、数据报的ID、数据报的序列号及ICMP数据段几个部分。进行报头打包的程序如下:ICMP回显请求的类型为8,即ICMP_ECHO;ICMP回显请求的代码值为0;ICMP回显请求的序列号是一个16位的值,通常由一个从0开始按1递增的值生成ICMP回显请求的ID用于区别不同进程请求应答,通常由进程的PID填充。将发送的时间存储在报文的数据部。进行校验和校验将校验和存储在结构体中。intpack(intpack_no){inti,packsize;structicmp*icmp;structtimeval*tval;icmp=(structicmp*)sendpacket;icmp-icmp_type=ICMP_ECHO;//消息类型为ICMP回显请求icmp-icmp_code=0;//code为0icmp-icmp_cksum=0;//校验和初始值为0icmp-icmp_seq=pack_no;//序列号icmp-icmp_id=pid;//进程pidpacksize=8+datalen;tval=(structtimeval*)icmp-icmp_data;gettimeofday(tval,NULL);//获取发送数据时间icmp-icmp_cksum=cal_chksum((unsignedshort*)icmp,packsize);//计算校验和returnpacksize;}校验和计算:ICMP校验和计算是对16位的数据进行累加计算并返回计算结果,对于奇数个字节数据的计算,是将最后的有效数据作为最高位的字节,低字节填充0。unsignedshortcal_chksum(unsignedshort*addr,intlen){intnleft=len;intsum=0;unsignedshort*w=addr;unsignedshortcheck_sum=0;/*以2字节为单位累加起来*/while(nleft1){sum+=*w++;nleft-=2;}if(nleft==1)//是否为奇数{*(unsignedchar*)(&check_sum)=*(unsignedchar*)w;sum+=check_sum;}sum=(sum16)+(sum&0xFFFF);//高低位相加sum+=(sum16);//加入溢出位check_sum=~sum;returncheck_sum;//取反}发送报文:将打包好的数据通过原始套接字发送到指定地址,使用sendto函数。每次发送成功后序列号增加1,即nsend++voidsend_packet(){intpacketsize;if(nsendMAX_NO_PACKETS){nsend++;//发送序列号加1packetsize=pack(nsend);//将数据打包if(sendto(sockfd,sendpacket,packetsize,0,(structsockaddr*)&dest_addr,sizeof(dest_addr))0)//发送数据包{perror(sendtoerror);}}}接收报文:接受报文:接受报文在接收数据包的值小于发送数据包的值时,继续接收数据包,通过recvfrom函数将接收到的数据存储到recvpacket中,将发送数据端的IP地址存储在from中,记录接收数据包的时间,调用unpack函数对数据包进行解包和数据分析。接收一个数据包接收序列号加1.voidrecv_packet(){intn,fromlen;externinterror;fromlen=sizeof(from);if(nreceivednsend)//接收报文少于发送报文,继续接收{if((n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(structsockaddr*)&from,&fromlen))0)//接收报文{perror(recvfromerror);}gettimeofday(&tvrecv,NULL);//保存接收数据包时间unpack(recvpacket,n);//解压数据包,并数据处理nreceived++;//接收序列号加1}}解压数据包:参数buf为包括ip头部的ip数据报文,len为数据长度,可以利用ip头部的参数快速地跳到ICMP报文部分,ip结构的ip_hl标识ip头部的长度,因此ip_hl标识的为4字节单位,所以通过乘以4获得ICMP报文的地址。获得报文数据后,应该判断其类型是否为ICMP_RCHOREPLY,并核实其标识是否为本进程的PID。由于需要计算报文往返时间,在本程序中需要先查找数据包的发送时间与接收时间进行计算得到报文往返时间。打印输出结果intunpack(char*buf,intlen){inti;intiphdrlen;structip*ip;structicmp*icmp;structtimeval*tvsend;doublertt;ip=(structip*)buf;iphdrlen=ip-ip_hl2;//获取ip报文头的大小=ip_hl*4icmp=(structicmp*)(buf+iphdrlen);//获取icmp报文地址len-=iphdrlen;//icmp报文长度if(len8){printf(ICMPpacket\'slengthislessthan8\n);return-1;}/*检查消息类型和进程号是否匹配*/if((icmp-icmp_type==ICMP_ECHOREPLY)&&(icmp-icmp_id==pid)){tvsend=(structtimeval*)icmp-icmp_data;//获取发送数据时间tv_sub(&tvrecv,tvsend);//获得时间差并保存在接收时间结构中rtt=tvrecv.tv_sec*1000+tvrecv.tv_usec/1000;temp_rtt[nreceived]=rtt;//保存暂时时间,方便统计all_time+=rtt;printf(%dbytesfrom%s:icmp_seq=%uttl=%dtime=%.1fms\n,//打印回显信息len,inet_ntoa(from.sin_addr),icmp-icmp_seq,ip-ip_ttl,rtt);icmp-icmp_seq,ip-ip_ttl,rtt);}elsereturn-1;}计算时间差:因为需要通过时间差来评估网络状况,在发送时保存发送时间,接收报文后通过计算获得报文往返时间用来评估当前网络状况。voidtv_sub(structtimeval*recvtime,structtimeval*sendtime){/*计算差值*/longsec=recvtime-tv_sec-sendtime-tv_sec;longusec=recvtime-tv_usec-sendtime-tv_usec;if(usec=0){recvtime-tv_sec=sec;recvtime-tv_usec=usec;}/*如果接收时间usec小于发送的usec,从sec借位*/else{recvtime-tv_sec=sec-1;recvtime-tv_usec=-usec;}}结果统计:通过对结果进行处理voidstatistics(intsig){doublesum_avg=0;inti;min=max=temp_rtt[0];avg=all_time/nreceived;for(i=0;inreceived;i++){if(temp_rtt[i]min)min=temp_rtt[i];elseif(temp_rtt[i]max)max=temp_rtt[i];if((temp_rtt[i]-avg)0)sum_avg+=avg-temp_rtt[i];elsesum_avg+=temp_rtt[i]-avg;}mdev=sum_avg/nreceived;printf(\n------%spingstatistics------\n,addr[0]);printf(%dpacketstransmitted,%dreceived,%d%%packetloss,time%.fms\n,nsend,nreceived,(nsend-nreceived)/nsend*100,all_time);printf(rttmin/avg/max/mdev=%.3f/%.3f/%.3f/%.3fms\n,min,avg,max,mdev);close(sockfd);exit(1);}主程序:1.参数处理通过getprotobyname()函数获得icmp对应的ICMP协议值,gethostbyname函数获得DNS对应的IP地址,inet_addr函数获得字符串类型ip地址对应的整形值。2.建立原始套接字,并通过套接字选项增大接收缓冲区。3.循环发送接收。main(intargc,char*argv[]){structhostent*host;structprotoent*protocol;unsignedlonginaddr=0;intsize=50*1024;addr[0]=argv[1];if(argc2)//判断参数个数{printf(usage:%shostname/IPaddress\n,argv[0]);exit(1);}if((protocol=getprotobyname(icmp))==NULL)//获取协议值{perror(getprotobyname);exit(1);}if((sockfd=socket(AF_INET,SOCK_RAW,protocol-p_proto))0)//建立原始套接字{perror(socketerror);exit(1);}setuid(getuid());setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));//增大接收缓冲区bzero(&dest_addr,sizeof(dest_addr));//初始化dest_addr.sin_family=AF_INET;//协议族if(inet_addr(argv[1])==INADDR_NONE)//判断是否为DNS{if((host=gethostbyname(argv[1]))==NULL)//如果为DNS转换为ip地址{perror(gethostbynameerror);exit(1);}//*给地址结构体赋值*/memcpy
本文标题:ping程序的实现
链接地址:https://www.777doc.com/doc-2850876 .html