ping程序的实现(Windows系统)

转载自b站up主  某科学的_半个人(已经过同意)

源文件

#include "he.h"
#include<iostream>
#include<windows.h>
using namespace std;
char IP[30];
const char* BToC(bool b)
{
	if (b)
	{
		return "true	";
	}
	else
	{
		return "false	";
	}
}
int main()
{

	//输入
	cout << "IP:";
	cin >> IP;
	int counts = 5, i = 0; // 发送次数
	while (i < counts)
	{
		PingAPI PA;
		PingReply* RP = new PingReply();
		cout << BToC(PA.Ping(IP, RP));
		cout << "Reply from " << IP << ": bytes=" << RP->m_dwBytes << "  time=" << RP->m_dwRoundTripTime << "ms  TTL=" << RP->m_dwTTL << "\n";
		Sleep(1000);
		i++;
	}

}

头文件

#pragma once
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include<WinSock2.h>
#pragma comment(lib, "WS2_32")  //连接到WS2_32.lib
#include<iostream>

#define DEF_PACKET_SIZE 32  //定义了 ICMP 数据包的大小
#define ECHO_REQUEST 8      //echo 请求的类型代码
#define ECHO_REPLY 0		//echo 响应的类型代码

struct IPHeader
{
	BYTE m_byVerHLen;   //4位版本+4位首部长度
	BYTE m_byTOS;       //服务类型
	USHORT m_usTotalLen;//总长度
	USHORT m_usID;      //标识
	USHORT m_usFlagFragOffset;//3位标志+3位片位移
	BYTE m_byTTL;       //TTL
	BYTE m_byProtocol;       //协议
	USHORT m_usHChecksum;//首部校验和
	ULONG m_ulSrcIP;    //源IP地址
	ULONG m_ulDestIP;    //目的IP地址
};
struct ICMPHeader
{
	BYTE m_byType;   //类型
	BYTE m_byCode;       //代码
	USHORT m_usChecksum;//校验和
	USHORT m_usID;      //标识符
	USHORT m_usSeq;      //序号   主要用于区分连续 ping 的时候发出的多个数据包
	ULONG m_ulTimeStamp;    //时间戳
};
struct PingReply
{
	USHORT m_usSeq;
	DWORD m_dwRoundTripTime = 0;
	DWORD m_dwBytes = 0;
	DWORD m_dwTTL = 0;

};
class PingAPI
{
public:
	PingAPI()
	{
		WSADATA WSAData;    //用于存储 Winsock 库的信息
		if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0)   //调用初始化 Winsock 库
		{
			printf("WSAStartup() failed: %d\n", GetLastError());
			return;
		}
		m_event = WSACreateEvent();  // 创建一个事件对象,用于后续的事件驱动网络操作
		m_usCurrentProcID = (USHORT)GetCurrentProcessId(); //获取当前进程的 ID
		m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0);
		/*创建一个原始套接字,用于发送和接收 ICMP 数据包。AF_INET 指定了地址族(IPv4),
		SOCK_RAW 指定了套接字类型(原始套接字),IPPROTO_ICMP 指定了协议类型(ICMP)*/
		if (m_sockRaw == INVALID_SOCKET)
		{
			std::cerr << "Socket creation failed: " << GetLastError() << std::endl;
		}
		else
		{
			WSAEventSelect(m_sockRaw, m_event, FD_READ);
			m_bIsInitSucc = TRUE;
			m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader));
			//分配内存用于构建 ICMP 数据包。DEF_PACKET_SIZE 是数据部分的大小,sizeof(ICMPHeader) 是 ICMP 头部的大小。

			if (m_szICMPData == NULL)
			{
				m_bIsInitSucc = FALSE;
			}
		}
	}
	~PingAPI()
	{
		WSACleanup();
		if (NULL != m_szICMPData)
		{
			free(m_szICMPData);
			m_szICMPData = NULL;
		}
	}
	BOOL Ping(DWORD dwDestIP, PingReply* pPingReply = NULL, DWORD dwTimeout = 2000)
	{
		return PingCore(dwDestIP, pPingReply, dwTimeout);
	}
	BOOL Ping(const char* szDestIP, PingReply* pPingReply = NULL, DWORD dwTimeout = 2000)
	{
		if (NULL != szDestIP)
		{
			return PingCore(inet_addr(szDestIP), pPingReply, dwTimeout);
		}
		return FALSE;
	}

private:
	BOOL PingCore(DWORD dwDestIP, PingReply* pPingReply, DWORD dwTimeout)
	{ //判断初始化是否成功
		if (!m_bIsInitSucc)
		{
			return FALSE;
		}
		//配置SOCKET
		sockaddr_in sockaddrDest;						//sockaddr_in 结构体变量,用于存储目标地址信息
		sockaddrDest.sin_family = AF_INET;				//设置地址族为 IPv4
		sockaddrDest.sin_addr.s_addr = dwDestIP;		//设置目标 IP 地址
		int nSockaddrDestSize = sizeof(sockaddrDest);	//获取目标地址结构体的大小,以便在 sendto 函数中使用

		//构建ICMP包
		int nICMPDataSize = DEF_PACKET_SIZE + sizeof(ICMPHeader);  //ICMP 数据包的总大小
		ULONG ulSendTimestamp = GetTickCountCalibrate();		   //获取当前时间戳,用于测量往返时间
		USHORT usSeq = ++s_usPacketSeq;							   //递增序列号,用于标识此次 ping 请求
		memset(m_szICMPData, 0, nICMPDataSize);                    //将分配的 ICMP 数据包缓冲区清零

		ICMPHeader* pICMPHeader = (ICMPHeader*)m_szICMPData;       //将缓冲区的开始处视为 ICMP 头部结构体的指针
		pICMPHeader->m_byType = ECHO_REQUEST;                      //设置 ICMP 类型为 echo 请求
		pICMPHeader->m_byCode = 0;
		pICMPHeader->m_usID = m_usCurrentProcID;
		pICMPHeader->m_usSeq = usSeq;								//序列号
		pICMPHeader->m_ulTimeStamp = ulSendTimestamp;				//时间戳
		pICMPHeader->m_usChecksum = CalCheckSum((USHORT*)m_szICMPData, nICMPDataSize);//校验和
		//发送ICMP报文
		if (sendto(m_sockRaw, m_szICMPData, nICMPDataSize, 0, (struct sockaddr*)&sockaddrDest, nSockaddrDestSize) == SOCKET_ERROR)
		{
			/*m_sockRaw:这是一个原始套接字(raw socket),用于发送 ICMP 请求。
				m_szICMPData:这是一个缓冲区,包含了要发送的 ICMP 数据包。
				nICMPDataSize:这是 ICMP 数据包的大小。
				0:这是发送操作的标志,通常用于指定发送操作的特定行为。在这里,0 表示没有特定的标志。
				(struct sockaddr*)&sockaddrDest:这是指向目的地地址的指针,它是一个 sockaddr_in 结构体,包含了目标 IP 地址和端口号(在这个例子中,端口号通常设置为 0,因为 ICMP 不使用端口号)。
				nSockaddrDestSize:这是目的地地址结构体的大小。*/
			return FALSE;
		}
		//判断是否需要接收相应报文
		if (pPingReply == NULL)
		{
			return TRUE;
		}

		char recvbuf[256] = { "\0" };
		while (TRUE)
		{
			//接收响应报文
			if (WSAWaitForMultipleEvents(1, &m_event, FALSE, 100, FALSE) != WSA_WAIT_TIMEOUT)
			{
				WSANETWORKEVENTS netEvent;
				WSAEnumNetworkEvents(m_sockRaw, m_event, &netEvent);  //枚举并填充
				if (netEvent.lNetworkEvents & FD_READ)
				{
					ULONG nRecvTimestamp = GetTickCountCalibrate();  //获取当前时间戳,用于计算往返时间
					int nPacketSize = recvfrom(m_sockRaw, recvbuf, 256, 0, (struct sockaddr*)&sockaddrDest, &nSockaddrDestSize);
					//从套接字接收数据到 recvbuf 缓冲区。256 是缓冲区大小,这里设置为 256 字节
					if (nPacketSize != SOCKET_ERROR)
					{
						IPHeader* pIPHeader = (IPHeader*)recvbuf;
						USHORT usIPHeaderLen = (USHORT)((pIPHeader->m_byVerHLen & 0x0f) * 4); //计算 IP 头部的长度。
						ICMPHeader* pICMPHeader = (ICMPHeader*)(recvbuf + usIPHeaderLen);  //定位 ICMP 头部在数据包中的位置。

						if (pICMPHeader->m_usID == m_usCurrentProcID //是当前进程发出的报文
							&& pICMPHeader->m_byType == ECHO_REPLY //是ICMP响应报文
							&& pICMPHeader->m_usSeq == usSeq //是本次请求报文的响应报文
							)
						{
							pPingReply->m_usSeq = usSeq;
							pPingReply->m_dwRoundTripTime = nRecvTimestamp - pICMPHeader->m_ulTimeStamp;//计算往返时间
							pPingReply->m_dwBytes = nPacketSize - usIPHeaderLen - sizeof(ICMPHeader);//数据长度=总长度-IP头部长度-ICMP头部长度
							pPingReply->m_dwTTL = pIPHeader->m_byTTL;
							return TRUE;
						}
					}
				}
			}
			if (GetTickCountCalibrate() - ulSendTimestamp >= dwTimeout)  //超时
			{
				return FALSE;
			}
		}


	}
	ULONG GetTickCountCalibrate()
	{
		static ULONG s_ulFirstCallTick = 0;
		static LONGLONG s_ullFirstCallTickMS = 0;
		SYSTEMTIME systemtime;
		FILETIME filetime;
		GetLocalTime(&systemtime);
		SystemTimeToFileTime(&systemtime, &filetime);
		LARGE_INTEGER liCurrentTime;
		liCurrentTime.HighPart = filetime.dwHighDateTime;
		liCurrentTime.LowPart = filetime.dwLowDateTime;
		LONGLONG llCurrentTimeMS = liCurrentTime.QuadPart / 10000;
		if (s_ulFirstCallTick == 0)
		{
			s_ulFirstCallTick = GetTickCount();
		}
		if (s_ullFirstCallTickMS == 0)
		{
			s_ullFirstCallTickMS = llCurrentTimeMS;
		}
		return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS - s_ullFirstCallTickMS);
	}
	USHORT CalCheckSum(USHORT* pBuffer, int nSize)
	{
		unsigned long ulCheckSum = 0;
		while (nSize > 1)
		{
			ulCheckSum += *pBuffer++;
			nSize -= sizeof(USHORT);
		}
		if (nSize)
		{
			ulCheckSum += *(UCHAR*)pBuffer;
		}
		ulCheckSum = (ulCheckSum >> 16) + (ulCheckSum & 0xffff);
		ulCheckSum += (ulCheckSum >> 16);

		return (USHORT)(~ulCheckSum);

	}
private:
	SOCKET m_sockRaw;
	WSAEVENT m_event;
	USHORT m_usCurrentProcID;
	char* m_szICMPData;
	BOOL m_bIsInitSucc;
private:
	USHORT s_usPacketSeq = 0;
};

运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUAA-真昼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值