ping功能实现(windows网路编程学习笔记)

一、概述
  ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
   ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于网络安全具有极其重要的意义。ICMP就是一个“错误侦测与回报机制”,其目的就是让我们能够检测网路的连线状况﹐也能确保连线的准确性。当路由器在处理一个数据包的过程中发生了意外,可以通过ICMP向数据包的源端报告有关事件。
  其功能主要有,侦测远端主机是否存在,建立及维护路由资料,重导资料传送路径(ICMP重定向),资料流量控制。ICMP在沟通之中,主要是透过不同的类别(Type)与代码(Code) 让机器来识别不同的连线状况。
  使用ping命令可以检测指定设备的在线状态(有些主机会屏蔽ping命令),但在写程序的时候一般不是调用cmd命令去直接ping主机,而是通过socket想目标机器发送ICMP请求包,然后等待返回结果。
二、实现
  1.定义ICMP数据包结构
  ICMP数据包包含在IP数据包结构内,为了在程序中对ICMP数据包做处理,我们使用结构体来描述IP数据包和ICMP数据包:
ICMP首部TYPE和CODE对应表:

TYPECODEDescription
00Echo Reply——回显应答(Ping应答)
30Network Unreachable——网络不可达
31Host Unreachable——主机不可达
32Protocol Unreachable——协议不可达
33Port Unreachable——端口不可达
34Fragmentation needed but no frag. bit set——需要进行分片但设置不分片比特
35Source routing failed——源站选路失败
36Destination network unknown——目的网络未知
37Destination host unknown——目的主机未知
38Source host isolated (obsolete)——源主机被隔离(作废不用)
39Destination network administratively prohibited——目的网络被强制禁止
310Destination host administratively prohibited——目的主机被强制禁止
311Network unreachable for TOS——由于服务类型TOS,网络不可达
312Host unreachable for TOS——由于服务类型TOS,主机不可达
313Communication administratively prohibited by filtering——由于过滤,通信被强制禁止
314Host precedence violation——主机越权
315Precedence cutoff in effect——优先中止生效
40Source quench——源端被关闭(基本流控制)
50Redirect for network——对网络重定向
51Redirect for host——对主机重定向
52Redirect for TOS and network——对服务类型和网络重定向
53Redirect for TOS and host——对服务类型和主机重定向
80Echo request——回显请求(Ping请求)
90Router advertisement——路由器通告
100Route solicitation——路由器请求
110TTL equals 0 during transit——传输期间生存时间为0
111TTL equals 0 during reassembly——在数据报组装期间生存时间为0
120IP header bad (catchall error)——坏的IP首部(包括各种差错)
121Required options missing——缺少必需的选项
130Timestamp request (obsolete)——时间戳请求(作废不用)
14Timestamp reply (obsolete)——时间戳应答(作废不用)
150Information request (obsolete)——信息请求(作废不用)
160Information reply (obsolete)——信息应答(作废不用)
170Address mask request——地址掩码请求
180Address mask reply——地址掩码应答

IP结构体:

typedef struct IPHDR{
	   unsigned int     h_len:4;	//包头长度
	   unsigned int     version:4;//版本号
	   unsigned char    tos://服务类型
	   unsigned short   total_len;//包总长度
	   unsigned short   ident;//唯一标识符
	   unsigned short   frag_and_flages;//标识
	   unsigned char    ttl;//生存时间
	   unsigned char    proto;//传输协议
	   unsigned short   checksum;//校验和
	   unsigned int     souceIP;//源ip
	   unsigned int     destIP;//目标ip
}IpHeader;

ICMP结构体:

typedef struct ICMPHDR{
	   BYTE      i_type;//类型
	   BYTE      i_code;//编码
	   USHORT    i_cksum;//校验和
	   USHORT    i_id;//编号
	   USHORT    i_seq;//序号
	   ULONG     timestamp;//时间戳
}IcmpHeader;

2.代码实现片段
填充ICMP包

/*
即初始化,先将头部字段进行赋值,再对数据部分进行填充,用'E'来填充,不要问我为什么,不求甚解,哈哈
#define ICMP_MIN 8						// ICMP包的最小长度为8个字节,只包含包头
#define DEF_PACKET_SIZE 32			// 执行ping操作时指定发送数据包的缺省大小
#define MAX_PACKET 1024				// 执行ping操作时指定发送数据包的最大大小
#define ICMP_ECHO 8						// 表示ICMP包为回射请求包
#define ICMP_ECHOREPLY 0			// 表示ICMP包为回射应答包
*/
void fullICMP(char *icmp_data,int datasize){
     IcmpHeader *icmpHdr;
     char *datapart;
     icmpHdr = (IcmpHeader*)icmp_data;
     memset(icmpHdr,0,datasize);
     icmp_hdr->i_type = ICMP_ECHO;
     icmp_hdr->i_id   = (SHORT)GetCurrentThread();
     icmp_hdr->i_code = 0;
     icmp_hdr->i_seq  = 0;
     icmp_hdr->i_sum  = 0;//校验和先设置为0
     datapart = icmp_data + sizeof(IcmpHeader);
     memset(datapart,'E',datasize - sizeof(IcmpHeader));
}

解析ICMp回应包

int decodeIcmpReply(char *buf,int bytes,DWORD tid){

   IpHeader *iphdr;					// IP数据包头
	   IcmpHeader *icmphdr;			// ICMP包头
   	unsigned short iphdrlen;		// IP数据包头的长度
	   iphdr = (IpHeader *)buf;		// 从buf中IP数据包头的指针
	   // 计算IP数据包头的长度 
	   iphdrlen = iphdr->h_len * 4 ; // number of 32-bit words *4 = bytes
	   
	   // 如果指定的缓冲区长度小于IP包头加上最小的ICMP包长度,则说明它包含的ICMP数据不完整,或者不包含ICMP数据
	   if (bytes < iphdrlen + ICMP_MIN) {
	      	return -1; 
	   }
	   
	   // 定位到ICMP包头的起始位置
	   icmphdr = (IcmpHeader*)(buf + iphdrlen);
	   
	   // 如果ICMP包的类型不是回应包,则不处理
	   if (icmphdr->i_type != ICMP_ECHOREPLY) {
	      	return -2;
	    }
   	// 发送的ICMP包ID和接收到的ICMP包ID应该对应
	   if (icmphdr->i_id != (USHORT)tid){ //(USHORT)GetCurrentProcessId()) {
		      return -3; 
	    }
	    // 返回发送ICMP包和接收回应包的时间差 
	    int time = GetTickCount() - (icmphdr->timestamp);
	   if(time >= 0)
		      return time;
   	else
	      	return -4; // 时间值不对
}

ping函数实现

int ping(const char *ip, DWORD timeout)
{
	   WSADATA wsaData;						// 初始化Windows Socket的数据
	   SOCKET sockRaw = NULL;			// 用于执行ping操作的套接字
	   struct sockaddr_in dest,from;		// socket通信的地址
	   struct hostent * hp;						// 保存主机信息
	   int datasize;									// 发送数据包的大小
	   char *dest_ip;								// 目的地址
	   char *icmp_data = NULL;				// 用来保存ICMP包的数据
	   char *recvbuf = NULL;					// 用来保存应答数据
	   USHORT seq_no = 0;
	   int ret = -1;
	   
	   // 初始化SOCKET
	   if (WSAStartup(MAKEWORD(2,1),&wsaData) != 0){
		      ret = -1000;// WSAStartup 错误
		      goto FIN;
	    }
	    
	   // 创建原始套接字
	   sockRaw = WSASocket (AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL, 0,WSA_FLAG_OVERLAPPED);
		   
	   if (sockRaw == INVALID_SOCKET) {
	     	ret = -2;// WSASocket 错误
	     	goto FIN;
	    }
	    
	    // 设置套接字的接收超时选项
	    int bread = setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));
	    
	    if(bread == SOCKET_ERROR) {
	      	ret = -3;// setsockopt 错误
	      	goto FIN;
	    }
	    
    	// 设置套接字的发送超时选项
    	bread = setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,
	    	sizeof(timeout));
	    if(bread == SOCKET_ERROR) {
	       	ret = -4;// setsockopt 错误
	        	goto FIN;
     	}
     	memset(&dest,0,sizeof(dest));

	    unsigned int addr=0;					// 将IP地址转换为网络字节序
    hp = gethostbyname(ip);				// 获取远程主机的名称
    	if (!hp){
       		addr = inet_addr(ip);
    	}
    	if ((!hp) && (addr == INADDR_NONE) ) {
      		ret = -5; // 域名错误
      		goto FIN;
    	}
	    // 配置远程通信地址
	    if (hp != NULL)
    	   	memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length);
    	else
	       	dest.sin_addr.s_addr = addr;

	    if (hp)
       		dest.sin_family = hp->h_addrtype;
    	else
       		dest.sin_family = AF_INET;
       	dest_ip = inet_ntoa(dest.sin_addr);

	     // 准备要发送的数据
     	datasize = DEF_PACKET_SIZE;
     	datasize += sizeof(IcmpHeader);
     	char icmp_dataStack[MAX_PACKET];
     	char recvbufStack[MAX_PACKET];
  	   icmp_data = icmp_dataStack;
     	recvbuf = recvbufStack;
	     // 未能分配到足够的空间
	     if (!icmp_data) {
	        	ret = -6; // 
	        	goto FIN;
      	}
     	memset(icmp_data,0,MAX_PACKET);
     	// 准备要发送的数据
     	fill_icmp_data(icmp_data,datasize);			// 设置报文头
     	((IcmpHeader*)icmp_data)->i_cksum = 0;
      	DWORD startTime = GetTickCount();
	     ((IcmpHeader*)icmp_data)->timestamp = startTime;
	     ((IcmpHeader*)icmp_data)->i_seq = seq_no++;
	     ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data,datasize);
	
	     // 发送数据
	     int bwrote;		
     	bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,
     		sizeof(dest));
    	if (bwrote == SOCKET_ERROR){
        		if (WSAGetLastError() != WSAETIMEDOUT) 
        		{
            			ret = -7; // 发送错误
            			goto FIN;
	         	}
	      }
      	if (bwrote < datasize ) {
         		ret = -8; // 发送错误
         		goto FIN;
       	}
      	// 使用QueryPerformance函数用于精确判断结果返回时间值
      	// 原有的其他的Windows函数(GetTickCount等)的方式返回值与Windows Ping应用程序相差太大。
      	LARGE_INTEGER ticksPerSecond;
      	LARGE_INTEGER start_tick;
      	LARGE_INTEGER end_tick;
      	double elapsed; // 经过的时间
      	QueryPerformanceFrequency(&ticksPerSecond); // CPU 每秒跑几个tick
      	QueryPerformanceCounter(&start_tick); // 开始时系统计数器的位置		
      	int fromlen = sizeof(from);			// 源地址的大小
      	while(1)
      	{
       		  // 接收回应包
       		   bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,			&fromlen);
       	   	if (bread == SOCKET_ERROR){
       		   	  if (WSAGetLastError() == WSAETIMEDOUT) {
           				     ret = -1; // 超时
           				     goto FIN;
	        		    }
	        		    ret = -9; // 接收错误
        			    goto FIN;
         		}
		       // 对回应的IP数据包进行解析,定位ICMP数据
	       	int time = decode_resp(recvbuf,bread,&from,GetCurrentThreadId());
	       	if( time >= 0 )	{
			         //ret = time;
			         QueryPerformanceCounter(&end_tick);  // 获取结束时系统计数器的值
			         elapsed = ((double)(end_tick.QuadPart - start_tick.QuadPart) / ticksPerSecond.QuadPart); // 计算ping操作的用时
			          ret = (int)(elapsed*1000);
			          goto FIN;
		         } else if(GetTickCount() - startTime >= timeout || GetTickCount() < startTime){
			              ret = -1; // 超时
		              	goto FIN;
		          }
	        }

        FIN:
	       // 释放资源
           	closesocket(sockRaw);
	           WSACleanup();
	           // 返回ping操作用时或者错误编号
           	return ret;
}

以下是自己写的ping指端网段的代码,欢迎讨论并指出不足之处:
ping命令实现

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值