MFC写的一个ping程序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36249516/article/details/78467009
前后创建了三次的MFC工程,就为了实现一个简单的ping 程序,第一天晚上因为头文件的包含关系,调了数个小时;第二次因为返回的数据有无也一直没有成功,经过两次十余小时的尝试,对整个流程有了更为深刻的了解后,再一次尝试后,过程就来的轻松地多。下面是具体流程:
首先先介绍一下简单的ping原理:
    部分转载于:http://www.cnblogs.com/ranjiewen/p/5704627.html
     C++实现ping功能:

基础知识

ping的过程是向目的IP发送一个type=8的ICMP响应请求报文,目标主机收到这个报文之后,会向源IP(发送方,我)回复一个type=0的ICMP响应应答报文。

那上面的字节、往访时间、TTL之类的信息又是从哪来的呢?这取决于IP和ICMP的头部。

 

IP头部:

头部内容有点多,我们关心的只有以下几个:

IHL:首部长度。因为IP的头部不是定长的,所以需要这个信息进行IP包的解析,从而找到Data字段的起始点。

    另外注意这个IHL是以4个字节为单位的,所以首部实际长度是IHL*4字节。

Time to Live:生存时间,这个就是TTL了。

Data:这部分是IP包的数据,也就是ICMP的报文内容。

 

ICMP响应请求/应答报文头部:

Type:类型,type=8表示响应请求报文,type=0表示响应应答报文。

Code:代码,与type组合,表示具体的信息,参考这里

Checksum:检验和,这个是整个ICMP报文的检验和,包括Type、Code、...、Data。

Identifier:标识符,这个一般填入本进程的标识符。

Sequence Number:序号

Data:数据部分

上面是标准的ICMP报文,一般而言,统计ping的往返时间的做法是,在ICMP报文的Data区域写入4个字节的时间戳。

在收到应答报文时,取出这个时间戳与当前的时间对比即可。

Ping程序实现步骤

  1. 创建类型为SOCK_RAW的一个套接字,同时设定协议IPPROTO_ICMP。
  2. 创建并初始化ICMP头。
  3. 调用sendto或WSASendto,将ICMP请求发给远程主机。
  4. 调用recvfrom或WSARecvfrom,以接收任何ICMP响应。
参考代码:

#include 
#include 
#include 
#pragma  comment (lib, "ws2_32.lib")

#define ICMP_ECHOREPLY	0  // ICMP回复应答
#define ICMP_ECHOREQ	8  // ICMP回应请求
#define REQ_DATASIZE 32		// 请求数据报大小

// 定义IP首部格式
typedef struct _IPHeader
{
	u_char  VIHL;			// 版本和首部长度
	u_char	ToS;			// 服务类型
	u_short	TotalLen;		// 总长度
	u_short	ID;			// 标识号
	u_short	Frag_Flags;		// 段偏移量
	u_char	TTL;			// 生存时间
	u_char	Protocol;		// 协议
	u_short	Checksum;		// 首部校验和
	struct	in_addr SrcIP;	// 源IP地址
	struct	in_addr DestIP;	// 目的地址
}IPHDR, *PIPHDR;


// 定义ICMP首部格式
typedef struct _ICMPHeader
{
	u_char	Type;			// 类型
	u_char	Code;			// 代码
	u_short	Checksum;		// 首部校验和
	u_short	ID;				// 标识
	u_short	Seq;			// 序列号
	char	Data;			// 数据
}ICMPHDR, *PICMPHDR;

// 定义ICMP回应请求
typedef struct _ECHOREQUEST
{
	ICMPHDR icmpHdr;
	DWORD	dwTime;
	char	cData[REQ_DATASIZE];
}ECHOREQUEST, *PECHOREQUEST;

// 定义ICMP回应答复 
typedef struct _ECHOREPLY
{
	IPHDR	ipHdr;
	ECHOREQUEST	echoRequest;
	char    cFiller[256];
}ECHOREPLY, *PECHOREPLY;

// 计算校验和
u_short checksum(u_short *buffer, int len)
{
	register int nleft = len;
	register u_short *w = buffer;
	register u_short answer;
	register int sum = 0;
    // 使用32bit的累加器,进行16bit的反馈计算
	while( nleft > 1 )  {
		sum += *w++;
		nleft -= 2;
	}
	// 补全奇数位
	if( nleft == 1 ) {
		u_short	u = 0;

		*(u_char *)(&u) = *(u_char *)w ;
		sum += u;
	}
   // 将反馈的16bit从高位移至地位
	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
	sum += (sum >> 16);			/* add carry */
	answer = ~sum;				/* truncate to 16 bits */
	return (answer);
}



// 发送回应请求函数
int SendEchoRequest(SOCKET s,struct sockaddr_in *lpstToAddr) 
{
	static ECHOREQUEST echoReq;
	static nId = 1;
	static nSeq = 1;
	int nRet;

	// 填充回应请求消息
	echoReq.icmpHdr.Type		= ICMP_ECHOREQ;
	echoReq.icmpHdr.Code		= 0;
	echoReq.icmpHdr.Checksum	= 0;
	echoReq.icmpHdr.ID			= nId++;
	echoReq.icmpHdr.Seq			= nSeq++;

	// 填充要发送的数据(随便填写)
	for (nRet = 0; nRet < REQ_DATASIZE; nRet++)
		echoReq.cData[nRet] = ' '+nRet;

	// 储存发送的时间
	echoReq.dwTime = GetTickCount();

	// 计算回应请求的校验和
	echoReq.icmpHdr.Checksum = checksum((u_short *)&echoReq, sizeof(ECHOREQUEST));

	// 发送回应请求  								  
	nRet = sendto(s,						// 建立起的套接字
				 (LPSTR)&echoReq,			// 发送的缓冲区内容
				 sizeof(ECHOREQUEST),
				 0,							// 标志位
				 (struct sockaddr*)lpstToAddr, // 发送的目标地址
				 sizeof(SOCKADDR_IN));   // 地址结构长度

	if (nRet == SOCKET_ERROR)
	{
		printf("sendto() error:%d\n",WSAGetLastError());
	}
	return (nRet);
}

// 接收应答回复并进行解析
DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL) 
{
	ECHOREPLY echoReply;
	int nRet;
	int nAddrLen = sizeof(struct sockaddr_in);

	//接收应答回复	
	nRet = recvfrom(s,					// 接收的套接字
					(LPSTR)&echoReply,	// 接收的缓冲区
					sizeof(ECHOREPLY),	// 缓冲区长度
					0,					// 标识
					(LPSOCKADDR)lpsaFrom,	// 接收的地址
					&nAddrLen);			// 地址结构长度

	// 检验接收结果
	if (nRet == SOCKET_ERROR) 
	{
		printf("recvfrom() error:%d\n",WSAGetLastError());
	}
    // 记录返回的TTL
	*pTTL = echoReply.ipHdr.TTL;
	//返回应答时间
	return(echoReply.echoRequest.dwTime);  		
}

// 等待回应答复,使用select机制
int WaitForEchoReply(SOCKET s)
{
	struct timeval timeout;
	fd_set readfds;

	readfds.fd_count = 1;
	readfds.fd_array[0] = s;
	timeout.tv_sec = 5;
    timeout.tv_usec = 0;

	return(select(1, &readfds, NULL, NULL, &timeout));
}

// Ping功能实现
void Ping(char *pstrHost)
{
	SOCKET	  rawSocket;
	LPHOSTENT lpHost;
	struct    sockaddr_in destIP;
	struct    sockaddr_in srcIP;
	DWORD	  dwTimeSent;
	DWORD	  dwElapsed;
	u_char    cTTL;
	int       nLoop;
	int       nRet;

	// 创建原始套接字,ICMP类型
	rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (rawSocket == SOCKET_ERROR) 
	{
		printf("socket() error:%d\n",WSAGetLastError());
		return;
	}
	
	// 检测目标主机
	lpHost = gethostbyname(pstrHost);
	if (lpHost == NULL)
	{
		printf("Host not found: %s\n", pstrHost);
		return;
	}
	
	// 设置目标机地址
	destIP.sin_addr.s_addr = *((u_long FAR *) (lpHost->h_addr));
	destIP.sin_family = AF_INET;
	destIP.sin_port = 0;

	// 提示开始进行Ping
	printf("\nPinging %s with %d bytes of data:\n",
				inet_ntoa(destIP.sin_addr),
				REQ_DATASIZE);


	// 发起多次Ping测试
	for (nLoop = 0; nLoop < 4; nLoop++)
	{
		//发送ICMP回应请求
		SendEchoRequest(rawSocket, &destIP);

		// 使用select()等待回复的数据
		nRet = WaitForEchoReply(rawSocket);
		if (nRet == SOCKET_ERROR)
		{
			printf("select() error:%d\n",WSAGetLastError());
			break;
		}
		if (!nRet)
		{
			printf("\nRequest time out");
			break;
		}

		//接收回复
		dwTimeSent = RecvEchoReply(rawSocket, &srcIP, &cTTL);

		// 计算花费的时间
		dwElapsed = GetTickCount() - dwTimeSent;
		printf("\nReply from %s: bytes=%d time=%ldms TTL=%d", 
               inet_ntoa(srcIP.sin_addr), 
			   REQ_DATASIZE,
               dwElapsed,
               cTTL);
	}

	printf("\n");
	nRet = closesocket(rawSocket); // 关闭套接字,释放资源
	if (nRet == SOCKET_ERROR)
	{
		printf("closesocket() error:%d\n",WSAGetLastError());
	}
}

void main(int argc, char **argv)
{
    WSADATA wsd;
    int nRet;

	// 检测输入的参数
    if (argc != 2)
    {
		printf("\nUsage: ping hostname\n");
		return;
    }
    // 初始化Winsock
	if (WSAStartup(MAKEWORD(1,1), &wsd) != 0)
    {
        printf("加载Winsock失败!\n");
    }

	//开始Ping
	Ping(argv[1]);

	// 释放Winsock资源
    WSACleanup();
}

参考博文有:http://blog.sina.com.cn/s/blog_47120f8f0101fjul.html
               http://blog.csdn.net/ivy8966/article/details/54612601?locationNum=6&fps=1
全部工程资源下载地址:   
     http://download.csdn.net/download/qq_36249516/10107387

猜你喜欢

转载自blog.csdn.net/qq_36249516/article/details/78467009