【计算机网络】网络诊断工具ping的模拟实现之具体细节
来源:互联网 发布:access sql limit 编辑:程序博客网 时间:2024/06/14 23:54
距离上次搭建框架已经过去了一个星期,在反复测试后,ping终于可以按照我所期望的这样来运行了。在搭建框架的时候,以为这个小项目不是很难,但最后在很多细节上花费了很多时间。
下来,就每部分做个总结。首先说主函数部分。
int main(int argc,char *argv[]){ if(argc!=2) { usage(argv[0]); return 1; } struct hostent *host=NULL; struct protoent *protocol=NULL; int size=128*K; protocol=getprotobyname("icmp"); if(protocol==NULL) { perror("getprotobyname"); return 2; } memcpy(dest_str,argv[1],strlen(argv[1])+1); memset(pingpacket,0,sizeof(pingm_packet)*128); rawsock=socket(AF_INET,SOCK_RAW,protocol->p_proto); if(rawsock<0) { perror("socket"); return 3; } pid=getpid(); setsockopt(rawsock,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size)); //增大接收缓冲区 bzero(&dest,sizeof(&dest)); dest.sin_family=AF_INET; unsigned long inaddr=inet_addr(argv[1]); if(inaddr==INADDR_NONE) { host=gethostbyname(argv[1]); if(host==NULL) { perror("gethostname"); return 4; } memcpy((char*)&dest.sin_addr,host->h_addr,host->h_length); } else { memcpy((char*)&dest.sin_addr,&inaddr,sizeof(inaddr)); } inaddr=dest.sin_addr.s_addr; printf("PING %s 56(84) bytes of data.\n",dest_str); 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 5; } err=pthread_create(&recv_id,NULL,icmp_recv,NULL); if(err<0) { return 6; } pthread_join(send_id,NULL); pthread_join(recv_id,NULL); close(rawsock); icmp_statistics(); return 0;}
最开始我想用一个while死循环去不断发送接收,但是在这里就会出现一些问题,比如说在Linux下可能会出现多线程问题,在发送还未执行的时候,已经开始接收了,程序接收不到东西就会崩溃等等问题,整个程序的健壮性都不太好。最后在这里,我选择去创建两个线程,两个线程只负责自己的事,一个只发送一个只接收。如果,发送因为网络问题一直没有发送成功,接收的线程就会一直在判断是否接收到报文,如果没有接收到报文,就会继续等着接收。这样的话,程序更加健壮,而且更贴切ping命令。
下来接着说发送部分。
void *icmp_send(){ gettimeofday(&tv_begin,NULL); while(alive) { int size=0; struct timeval tv; gettimeofday(&tv,NULL); //当前包发送的时间 pingm_packet *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"); continue; } packet_send++; sleep(1); }}
void icmp_pack(struct icmp *icmp,int seq,struct timeval *tv,int length){ unsigned char i=0; icmp->icmp_type=ICMP_ECHO; icmp->icmp_code=0; icmp->icmp_cksum=0; icmp->icmp_seq=seq; icmp->icmp_id=pid & 0xffff; for(i=0;i<length;i++) { icmp->icmp_data[i]=i; } icmp->icmp_cksum=icmp_cksum((unsigned char*)icmp,length);// printf("send: type: %d, code: %d, sum: %d, seq: %d, id: %d\n", \ icmp->icmp_type, icmp->icmp_code, icmp->icmp_cksum, \ icmp->icmp_seq, icmp->icmp_id);// fflush(stdout);}
在发送部分,值得注意的是在我们发送一个icmp报文的时候,需要做的事情。第一是要按照icmp报文的格式去构建一个报文,将信息填充进去,在填充信息的时候,一定要明白发送的时候,type和code都是0,type是ICMP_ECHO。这部分报文的知识,可以去查看【计算机网络】网络诊断工具ping的模拟实现之基础知识。
unsigned short icmp_cksum(unsigned char* data,int len){ int sum=0; int old=len & 0x01; //判断是否为奇数 while(len & 0xfffe) //将数据按照2个字节为单位累加 { sum+=*(unsigned short*)data; data+=2; len-=2; } if(old) //如果是奇数,则需要对最后的一个数据进行处理 { unsigned short tmp=((*data)<<8)&0xff00; sum+=tmp; } sum=(sum>>16)+(sum & 0xffff); //将高低位相加 sum+=(sum>>16); //将最高位相加 return ~sum;}
校验算法是采用的是TCP/IP校验最经典的,将所有位和累加计算,然后返回。
终于要说接收部分了,重中之重!
void *icmp_recv(){ 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("recv"); continue; } ret=icmp_unpack(recv_buff,size); if(ret==-1) continue; } break; } }}
当有报文发送,我们就一直处于接收状态。采用select可以使所有可读的报文排队依次进行读取。然后我们依次进行读取信息。在经过解包处理,即可将报文信息展示出来。接下来就说说这个解包函数。
int icmp_unpack(char *buf,int len){ int i=0; int iphl=0; struct ip *ip=NULL; struct icmp *icmp=NULL; int rtt=0; ip=(struct ip*)buf; iphl=ip->ip_hl<<2; icmp=(struct icmp*)(buf+iphl); len-=iphl; if(len<8) { printf("icmp packet length is less than 8\n"); return -1; }// printf("recv: type: %d, code: %d, sum: %d, seq: %d, id: %d\n",\ icmp->icmp_type, icmp->icmp_code, icmp->icmp_cksum,\ icmp->icmp_seq, icmp->icmp_id);// fflush(stdout); if((icmp->icmp_type==ICMP_ECHOREPLY)&&(icmp->icmp_id==pid)) { struct timeval tv_interval; struct timeval tv_send; struct timeval tv_recv; pingm_packet *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; tmp_rtt[packet_recv]=rtt; all_time+=rtt; packet_recv++; 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); } else return -1;}
在这里我们是将读取的信息存储在一个buf里,然后去分离报头部分,判断该报文是不是我们发送的,如果可以确定是我们发送的,就可以获取报文的信息了,通过报文计算往返时间、得到报文的长度、第几次报文等等信息。在这里,我建立了一个查找报文的函数icmp_findpacket,因为每次发送的报文信息,都被我们保存在一个结构体里,然后收到报文的时候,进行二次判断,确认报文是我们发送的第几次报文的回应,以防报文不是我们发送的。
pingm_packet *icmp_findpacket(int seq) //查找报文{ int i=0; pingm_packet *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;}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; if(tv.tv_usec<0) //借位 { tv.tv_sec-=1; tv.tv_usec+=1000000; } return tv;}
整个代码的主要逻辑已经说完了,下面是几个显示函数,一个是计算最大最小平均时间,一个显示。
void icmp_statistics(){ long time=(tv_interval.tv_sec *1000)+(tv_interval.tv_usec/1000); cal_rtt(); 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); printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n",min,avg,max,mdev);}void cal_rtt(){ double sum_avg=0; int i=0; min=max=tmp_rtt[0]; avg=all_time/(double)packet_recv; for(i=0;i<(double)packet_recv;i++) { if(tmp_rtt[i]<min) min=tmp_rtt[i]; if(tmp_rtt[i]>max) max=tmp_rtt[i]; if((tmp_rtt[i]-avg)>0) sum_avg+=tmp_rtt[i]-avg; else sum_avg+=avg-tmp_rtt[i]; } mdev=sum_avg/packet_recv;}
总结一下,在这个小项目中,很多地方都是非常基础的。但是也往往是最容易写错的,一定要把握好打包解包时报文的正确性。在测试的时候,先可以将报文的信息打印出来进行查看,看发送的报文和接收的报文信息是否一致,逐步一点一点去测试。最后看下分别ping自己和ping百度的效果。
源码查看下载地址:ping的模拟实现源码
- 【计算机网络】网络诊断工具ping的模拟实现之具体细节
- 【计算机网络】网络诊断工具ping的模拟实现之基础知识
- 【计算机网络】网络诊断工具ping的模拟实现之搭建框架
- ping(网络诊断工具)
- ping工具模拟实现
- ping网络诊断常见故障
- 网络基本功(十四):细说诊断工具ping
- 网络基本功(十四):细说诊断工具ping
- 网络通信 - ping的具体过程
- 网络程序之ping指令的实现
- dos命令之 ping (网络诊断与配置) 使用详解
- 汽车诊断工具的实现
- 使用 Ping进行网络诊断
- 通过DL4J实现疾病的模拟诊断
- 网络诊断工具
- linux网络诊断工具
- iOS网络诊断功能 ping traceroute
- Exadata 的诊断工具之 sundiag.sh
- 《机器学习实战》学习笔记-[9]-回归-加权最小二乘LWLR
- Servlet常用的六个对象
- 求二叉树结点最大距离
- 常用Socket函数详解
- 高斯相关内容
- 【计算机网络】网络诊断工具ping的模拟实现之具体细节
- (104)反射:获取Class的对象、构造函数、字段、方法。反射实例练习
- asp.net4.0网站开发与项目实战—学习笔记1
- java高级特性之--自动拆装箱和枚举
- Working with Linear Models
- linux命令之 tree 命令
- 集群
- HDU 3853 概率DP 解题报告
- ToLua学习笔记,通信(二)