ping在类unix下的实现

前端之家收集整理的这篇文章主要介绍了ping在类unix下的实现前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
  1. 参考链接http://www.jb51.cc/article/p-ehzxwqqp-zr.html
  2. http://blog.csdn.net/petershina/article/details/8571562
  3. http://www.cnblogs.com/noble/p/4144139.html
  4. http://www.jb51.cc/article/p-qxsxpcrq-tm.html
  1. ping程序使用原始套接字,发送以太网首部+ip首部+icmp首部+56字节的数据
  2. 在发送时将icmp标志设置为进程pid,同时icmp序列号由1递增,数据部分为发送时的时间,在icmp应答报文中将请求回显报文中的标志位和数据原封不动的返回,便于计算往返时延rtt。
  3. icmp报头+数据 检验采用循环二进制反码求和
  4. 流程:创建通信的套接字-->将地址、端口信息与套接字绑定-->构建IP包头与ICMP包头-->发送构建的数据包-->接收对方主机的回应-->给出程序反馈信息
  5. 编译时要加上-lpthread
    包含头文件了,仅能说明有了线程函数的声明, 但是还没有实现, 加上-lpthread是在链接阶段,链接这个库
  6. 写注释时注意别在注释外面写全角空格和英文,编译器不识别
  7. protoent结构:
    struct protoent{
    	char *p_name; //网络协议名
    	char **p_aliases;//别名
    	int p_proto;//网络协议编号
    } 
    
  8. ip结构
    typedef struct _ip_hdr  
    {  
        #if LITTLE_ENDIAN  
        unsigned char ihl:4;     //首部长度  
        unsigned char version:4,//版本   
        #else  
        unsigned char version:4,//版本  
        unsigned char ihl:4;     //首部长度  
        #endif  
        unsigned char tos;       //服务类型  
        unsigned short tot_len;  //总长度  
        unsigned short id;       //标志  
        unsigned short frag_off; //分片偏移  
        unsigned char ttl;       //生存时间  
        unsigned char protocol;  //协议  
        unsigned short chk_sum;  //检验和  
        struct in_addr srcaddr;  //源IP地址  
        struct in_addr dstaddr;  //目的IP地址  
    }ip_hdr; 
    
  9. icmp结构
    typedef struct _icmphdr{
    unsigned char i_type; //8位类型,本实验用 8: ECHO 0:ECHO REPLY
    unsigned char i_code; //8位代码,本实验置零
    unsigned short i_cksum; //16位校验和,从TYPE开始,直到最后一位用户数据,如果为字节数为奇数则补充一位
    unsigned short i_id ; //识别号(一般用进程号作为识别号),用于匹配ECHO和ECHO REPLY包
    unsigned short i_seq ; //报文序列号,用于标记ECHO报文顺序
    unsigned int timestamp; //时间戳
    }ICMP_HEADER;
  10. 实现代码
    #include<stdio.h>
    #include<stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include<netinet/ip_icmp.h>
    #include<netdb.h>/*名字与地址转换*/
    #include<string.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<sys/time.h>
    #include<netinet/in.h>/*字节排序*/
    #include<arpa/inet.h>/*字符串与网络字节序转化*/
    #include<pthread.h>
    
    
    struct sockaddr_in dst_addr;
    struct sockaddr_in recv_addr;
    
    struct timeval tvrecv;
    char icmp_pkt[1024]={0};
    char recv_pkt[1024]={0};
    
    int sockfd=0,bytes=56,nsend_pkt = 0,nrecv_pkt = 0;
    pid_t pid;
    
    void statistics();
    int in_chksum(unsigned short * buf,int size);/*检验和算法*/
    int pack(int send_pkt);/*设置icmp报头*/
    void *send_ping();/*发送三个icmp报文*/
    int unpack(char *recv_pkt,int size);/*剥去icmp报头*/
    void *recv_ping();/*接受所有icmp报文*/
    void tv_sub(struct timeval *out,struct timeval *in);/*两个time_val结构相减*/
    
    /*struct protoent{
    	char *p_name; //网络协议名
    	char **p_aliases;//别名
    	int p_proto;//网络协议编号
    } */
    int main(int argc,char *argv[])
    {
    	int size = 50 * 1024;
    	int errno=-1;
    	int ttl=64;
    	void *tret;
    	pthread_t send_id,recv_id;
    	struct in_addr ipv4_addr;
    	struct hostent *ipv4_host;
    	struct protoent *protocol = NULL;
    
    	if(argc < 2 )
    	{
    		printf("Usage : ./ping <host>\n");
    		exit(1);
    	}
    	
    	if((protocol = getprotobyname("icmp")) == NULL)/*不可重入,线程不安全*/
    	{
    		printf("unkown protocol\n");
    		exit(1);
    	}
     /*使用原始套接字,只有root权限才可以创建,以太网首部+ip首部+icmp首部+56字节的数据,直接使用底层协议*/
    	if((sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto)) < 0)
    	{
    		printf("socket fail\n");
    		exit(1);
    	}
    	/*回收root权限,设置当前实际用户权限。*/
    	setuid(getuid());
    	/*扩大套件字接受缓冲区到50k,避免缓冲区溢出的可能性,若无意中ping一个广播或多播地址,可能会收到大量应答*/
    	setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));
    	/*指定为IPPROTO_IP级别,制定外出TTL。*/
    	setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));
       /*指定存活时间*/
    	setsockopt(sockfd,IP_TTL,sizeof(ttl));
    /*memset(&dst_addr,sizeof(dst_addr));*/
         bzero(&dst_addr,sizeof(dst_addr));
    	 dst_addr.sin_family=AF_INET;
    	 errno=inet_aton(argv[1],&ipv4_addr);
    
    	 if(errno == 0)
    	 {
    		 ipv4_host = gethostbyname(argv[1]);
    		 if(ipv4_host == NULL)
    		 {
    			 printf("connect: Invalid argument\n");
    			 return -1;
    		 }
    		 dst_addr.sin_addr=*((struct in_addr  *)ipv4_host->h_addr);
    	 }
    	 else
    	 {
    		 dst_addr.sin_addr= ipv4_addr;
    	 }
    
    	 pid=getpid();
    
    	 printf("PING %s (%s) %d bytes of data.\n",argv[1],inet_ntoa(dst_addr.sin_addr),bytes);
         signal(SIGINT,statistics);
    
    	 if((errno=pthread_create(&send_id,NULL,send_ping,NULL)) !=0)
    	 {
    		 printf("send_ping thread fail\n");
    		 exit(1);
    	 }
    
        if((errno=pthread_create(&recv_id,recv_ping,NULL)) !=0)
    	 {
    		 printf("recv_ping thread fail\n");
    		 exit(1);
    	 }
    
    	
    	 if((errno =pthread_join(send_id,&tret)) !=0)
    	 {
    		 printf("can't join with thread send");
    		 exit(1);
    	 }
    	
    	 if((errno =pthread_join(recv_id,&tret)) != 0)
    	 {
    		 printf("can't join with thread recv");
    		 exit(1);
    	 }
    
    	 return 0;
    }
    /*在收到终端信号时,输出ping 的检测状态 */   
    void statistics()
    {
    	printf("\n--- %s ping statistics ---\n",inet_ntoa(dst_addr.sin_addr));
    	printf("%d packets transmitted,%d recvived,%.3f%c packet loss\n",nsend_pkt,nrecv_pkt,(float)100*(nsend_pkt-nrecv_pkt)/nsend_pkt,'%');
    	close(sockfd);
    
    	exit(0);
    }
    /*校验和算法 icmp首部+数据*/
    int in_chksum(unsigned short *buf,int size)
    {
         int nleft=size;
    	 int sum=0;
    	 unsigned short *w=buf;
    	 unsigned short ans=0;
    	 /*将ICMP二进制数据以两个字节相加*/
    	 while(nleft > 1)
    	 {
    		 sum+=*w; /*相加16bits*/
    		 w++;  /* 移动sizeof(short)2个大小的字节*/
    		 nleft-=2;
    	 }
    	 /*若只剩下一个字节的数据,则将最后一个字节视为两个字节的高字节,低字节视为0,继续相加*/
    	 if(nleft == 1)
    	 {
    		 *(unsigned char *)(&ans)= *(unsigned char *)w;
    		 sum+=ans;
    	 }
    	 /*将高16位与低16位相加,可能产生进位,故第二次移位相加*/
    	 sum=(sum>>16)+(sum & 0xFFFF);
    	 sum+=(sum>>16);
    	 ans=~sum;
    	 return ans;
    }
    
    int pack(int send_pkt)
    {
       struct icmp *pkt= (struct icmp  *)icmp_pkt;
       struct timeval *time=NULL;
    
       pkt->icmp_type = ICMP_ECHO;
       pkt->icmp_code = 0;
       pkt->icmp_cksum= 0;
       pkt->icmp_seq= htons(send_pkt);
       pkt->icmp_id = pid;
       time = (struct timeval *)pkt->icmp_data;
       gettimeofday(time,NULL);/*记录发送时间*/
       pkt->icmp_cksum=in_chksum((unsigned short *)pkt,bytes + 8);/*进行校验*/
      
       return bytes + 8;
    }
    
    void *send_ping()
    {
    	int send_bytes=0;
    	int ret=-1;
    	while(1)
    	{
           nsend_pkt++;
    	   send_bytes= pack(nsend_pkt);
    
           ret=sendto(sockfd,icmp_pkt,send_bytes,(struct sockaddr *)&dst_addr,sizeof(dst_addr));
    	   if(ret == -1)
    		   printf("send fail \n");
    	   sleep(1);
    	}
    }
    void tv_sub(struct timeval *out,struct timeval *in)
    {
    	if((out->tv_usec-=in->tv_usec) < 0)
    	{
    		--out->tv_sec;
            out->tv_usec+=1000000;
    	}
    	out->tv_sec-=in->tv_sec;
    }
    
    int unpack(char *recv_pkt,int size)
    {
    	struct iphdr *ip= NULL;
    	int iphdrlen;
    	struct icmp *icmp;
    	struct timeval *tvsend;
    	double rtt;
    	ip = (struct iphdr *)recv_pkt;
    	iphdrlen = ip->ihl<<2;/*求ip报头的长度,单位为4个字节*/
    	icmp = (struct icmp *)(recv_pkt + iphdrlen);
    
    	size-=iphdrlen;
    	if(size <8)/*小于icmp报头长度则不合理*/
    	{
    		printf("ICMP size is less than 8\n");
    		exit(1);
    	}
    	if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid))
    	{
    		tvsend = (struct timeval *)icmp->icmp_data;
    		tv_sub(&tvrecv,tvsend);
    		rtt = tvrecv.tv_sec * 1000 + (double)tvrecv.tv_usec /(double)1000;
    		printf("%d byte from %s: icmp_seq = %d ttl=%d rtt=%.3fms\n",size,inet_ntoa(recv_addr.sin_addr),ntohs(icmp->icmp_seq),ip->ttl,rtt);
    	}
    		else
    		{
    			return -1;
    		}
    		return 0;
    }
    void *recv_ping()
    {
    	fd_set rd_set;
    	struct timeval time;
    	time.tv_sec = 5;
    	time.tv_usec =0;
        int ret = 0,nread =0,recv_len =0;
        recv_len= sizeof(recv_addr);
    	while(1)
    	{
    		FD_ZERO(&rd_set);
    		FD_SET(sockfd,&rd_set);
    		ret=select(sockfd+1,&rd_set,&time);
    
    		if(ret <= 0)
    			continue;
    			else if(FD_ISSET(sockfd,&rd_set))
    			{
    				nread=recvfrom(sockfd,recv_pkt,sizeof(recv_pkt),(struct sockaddr *)&recv_addr,(socklen_t *)&recv_len);
    				if(nread < 0)
    					continue;
    				gettimeofday(&tvrecv,NULL);
    				if(unpack(recv_pkt,nread) == -1)
    				  continue;
    				  nrecv_pkt++;
    			}
    	}
    }
运行结果:

猜你在找的Bash相关文章