Ping6程序的实现Ping6函数主要用于向一个节点发送回送请求报文并接收该节点回复的回送应答报文用于确定一个节点的可达性及往返时间延迟。回送请求报文格式如下:标识符和序列号由源节点产生,用于将请求报文和应答报文对应起来,其中数据可以是任意字节的任意数据。回送应答报文格式如下:回送应答报文的标识符和序列号及数据都是由请求报文中获得。在linux下ICMPv6的报文头结构如下:本程序中,报文简化为只包含消息类型、消息代码、数据报的ID、数据报的序列号及数据段几个部分。ICMPV6回送请求报文类型为128;ICMPV6回送请求报文代码值为0;ICMPV6回送请求报文序列号通常为一个递增的数生成ICMPV6回送请求报文的ID用于对应回送应答报文,通常用进程的PID补充同时保存发送时间。inticmpv6_pack(intpack_num){inti,packsize;structicmp6_hdr*icmpv6;structtimeval*tval;icmpv6=(structicmp6_hdr*)sendbuf;icmpv6-icmp6_type=128;//消息类型为ICMPv6回显请求icmpv6-icmp6_code=0;//code为0icmpv6-icmp6_cksum=0;//校验和初始值为0icmpv6-icmp6_seq=pack_num;//序列号icmpv6-icmp6_id=pid;//进程pidpacksize=8+datalen;gettimeofday(sendtime,NULL);//获取发送数据时间returnpacksize;}发送报文:将打包好的数据通过原始套接字发送到指定地址,使用sendto函数。每次发送成功后序列号增加1,即nsend++voidicmp6_send(){intpacketsize;if(nsendmax_loop_num){nsend++;//发送序列号加1packetsize=icmpv6_pack(nsend);//打包数据if(sendto(socksaw,sendbuf,packetsize,0,(structsockaddr*)&send_addr,sizeof(send_addr))0)//发送数据包{perror(sentoerror);}}}接受报文:接受报文在接收数据包的值小于发送数据包的值时,继续接收数据包,通过recvfrom函数将接收到的数据存储到recvbuf中,将发送数据端的IP地址存储在recv_addr中,记录接收数据包的时间,调用unpack函数对数据包进行解包和数据分析。接收到一个报文后接收序列号并没有加1,而是在解包后确定数据包正确后加1,防止接收错误的报文导致丢包的情况。voidicmp6_recv(){intn,fromlen;fromlen=sizeof(recv_addr);while(nrecvnsend)//接收报文少于发送报文,继续接收{if((n=recvfrom(socksaw,recvbuf,sizeof(recvbuf),0,(structsockaddr*)&recv_addr,&fromlen))0)//接收报文{perror(recvfromerror);}gettimeofday(&recvtime,NULL);//保存接收数据包时间unpack(recvbuf,n);//解压数据包,并数据处理}}解压收到的报文:接收到报文后判断报文长度是不是太短,如果不是证明报文有效,需要判断报文的类型是否为回送应答报文即类型号是不是129,并核实其标识ID是否是本进程PID,确定报文正确后,接收序列号加1.通过接收报文的时间减去发送报文的时间,可以得到报文往返时间。最后是对返回信息进行显示,因为ipv6为128个字节,中间为0的位一般用::代替,为了显示美观,采用了标志位flag进行判断,达到显示标准ipv6地址格式的目的。intunpack(char*buf,intlen){inti;intiphdrlen;structip6*ip6;structicmp6_hdr*icmpv6;doublertt;intflag=0;icmpv6=(structicmp6_hdr*)(buf);//获得ICMPv6报文地址if(len8){printf(ICMPpacker\'slengthislessthan8\n);return(-1);}/*检查消息类型和进程号是否匹配*/if((icmpv6-icmp6_type==129)&&(icmpv6-icmp6_id==pid)){nrecv++;//判断数据有效,接收序列号加1different.tv_sec=recvtime.tv_sec-sendtime-tv_sec;different.tv_usec=recvtime.tv_usec-sendtime-tv_usec;//计算时间差rtt=different.tv_sec*1000.0+different.tv_usec/1000.0;printf(%dbytesfrom:,len);for(i=0;i16;i++)//打印ipv6地址{if(recv_addr.sin6_addr.s6_addr[i]==0&&flag==0){printf(::);flag=1;}elseif(recv_addr.sin6_addr.s6_addr[i]==0&&flag==1){flag=flag;}elseif(recv_addr.sin6_addr.s6_addr[i]!=0&&flag==1){printf(%02x,recv_addr.sin6_addr.s6_addr[i]);flag=2;}else{printf(%02x,recv_addr.sin6_addr.s6_addr[i]);}}printf(icmp_seq=%utime=%1.4fms\n,icmpv6-icmp6_seq,rtt);}}主函数:主函数主要包括以下几个部分:(1)对参数个数进行判断,判断参数是否有效。(2)通过套接字选项设置发送和接收超时选项。(3)进行地址处理,首先通过inet_pton函数将字符串格式的地址转换为二进制地址,然后拷贝到对应套接字地址结构体,为发送数据准备。(4)循环发送接收max_loop_num次。voidmain(intargc,char*argv[]){switch(argc)//判断参数个数,{case3:max_loop_num=atoi(argv[2]);case2:printf(~~~receice1parameter:\n);printf(~~~param1:%s\n,argv[1]);printf(\n);tv.tv_sec=5;tv.tv_usec=0;break;default:printf(~~~pleaseinputcorrectparam\n);exit(0);break;}socksaw=socket(PF_INET6,SOCK_RAW,IPPROTO_ICMPV6);//建立套接字setsockopt(socksaw,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(structtimeval));//接收超时setsockopt(socksaw,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(structtimeval));//发送超时send_addr.sin6_family=AF_INET6;addrptr=inet_pton(AF_INET6,argv[1],&addr);//地址转换if(addrptr0){printf(inet_pton:ip,%s\n,argv[1]);}else{printf(erraddr:%d\n,addrptr);}memcpy(&send_addr.sin6_addr,&addr,sizeof(addr));//获取地址pid=getpid();//进程PIDwhile(nsendmax_loop_num){sleep(1);icmp6_send();//数据发送icmp6_recv();//数据接收}printf(%dpacketstransmitted,%dreceived,%d%%packetloss\n,nsend,nrecv,(nsend-nrecv)/nsend*100);close(socksaw);exit(1);}编译运行:结果如下