C++ 实现的Ping类的封装
Ping 使用 Internet 控制消息协议(ICMP)来测试主机之间的连接。当用户发送一个 ping 请求时,则对应的发送一个 ICMP Echo 请求消息到目标主机,并等待目标主机回复一个 ICMP Echo 回应消息。如果目标主机接收到请求并且网络连接正常,则会返回一个回应消息,表示主机之间的网络连接是正常的。如果目标主机没有收到请求消息或网络连接不正常,则不会有回应消息返回。
编译报错问题解决
在Windows环境下编程不可避免的会用到windows.h和winsock.h头文件,在默认情况下windows.h头文件会包含winsock.h,此时当尝试包含winsock.h时就会出现头文件定义冲突的情况。解决这个冲突的方式有两种,第一种,在头部定义#define WIN32_LEAN_AND_MEAN来主动去除winsock.h头文件包含。第二种是将#include <winsock2.h>头文件,放在#include<windows.h>之前。两种方式均可,这些方法在进行Windows套接字编程时非常重要,可以防止头文件冲突,确保编译顺利进行。
Ping头文件
如下头文件代码定义了几个结构体,用于表示IP协议头、ICMP协议头和Ping的回复信息。这些结构体主要用于网络编程中,解析和构建网络数据包。
#pragma once
#include <winsock2.h>
#pragma comment(lib, "WS2_32")
#define DEF_PACKET_SIZE 32
#define ECHO_REQUEST 8
#define ECHO_REPLY 0
struct IPHeader
{
BYTE m_byVerHLen; // 4位版本+4位首部长度
BYTE m_byTOS; // 服务类型
USHORT m_usTotalLen; // 总长度
USHORT m_usID; // 标识
USHORT m_usFlagFragOffset; // 3位标志+13位片偏移
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; // 序号
ULONG m_ulTimeStamp; // 时间戳(非标准ICMP头部)
};
struct PingReply
{
USHORT m_usSeq; // 来源IP
DWORD m_dwRoundTripTime; // 时间戳
DWORD m_dwBytes; // 返回长度
DWORD m_dwTTL; // TTL值
};
class CPing
{
public:
CPing(); // 构造函数
~CPing(); // 析构函数
// 执行 Ping 操作的方法,传入目标 IP 地址或域名、PingReply 结构体和超时时间
BOOL Ping(DWORD dwDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000);
BOOL Ping(char *szDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000);
private:
// Ping 核心方法,传入目标 IP 地址、PingReply 结构体和超时时间
BOOL PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout);
// 计算检验和的方法,传入缓冲区和大小
USHORT CalCheckSum(USHORT *pBuffer, int nSize);
// 获取时钟计时器的校准值
ULONG GetTickCountCalibrate();
private:
SOCKET m_sockRaw; // 原始套接字
WSAEVENT m_event; // WSA 事件
USHORT m_usCurrentProcID; // 当前进程 ID
char *m_szICMPData; // ICMP 数据
BOOL m_bIsInitSucc; // 初始化是否成功
private:
static USHORT s_usPacketSeq; // 静态变量,用于记录 ICMP 包的序列号
};
下面是对每个结构体成员的简要说明:
- IPHeader 结构体:
m_byVerHLen: 4位版本号 + 4位首部长度。m_byTOS: 服务类型。m_usTotalLen: 总长度。m_usID: 标识。m_usFlagFragOffset: 3位标志 + 13位片偏移。m_byTTL: 生存时间。m_byProtocol: 协议类型。m_usHChecksum: 首部检验和。m_ulSrcIP: 源IP地址。m_ulDestIP: 目的IP地址。
- ICMPHeader 结构体:
m_byType: ICMP类型。m_byCode: ICMP代码。m_usChecksum: 检验和。m_usID: 标识符。m_usSeq: 序号。m_ulTimeStamp: 时间戳(非标准ICMP头部)。
- PingReply 结构体:
m_usSeq: 序列号。m_dwRoundTripTime: 往返时间。m_dwBytes: 返回长度。m_dwTTL: TTL值。
这些结构体主要用于在网络编程中处理与IP、ICMP和Ping相关的数据包。在实际应用中,可以使用这些结构体来解析接收到的网络数据包,或者构建要发送的数据包。
类成员说明:
m_sockRaw: 用于发送原始套接字的成员变量。m_event: WSA 事件。m_usCurrentProcID: 当前进程 ID。m_szICMPData: ICMP 数据。m_bIsInitSucc: 初始化是否成功的标志。s_usPacketSeq: 静态变量,用于记录 ICMP 包的序列号。
类方法说明:
Ping: 执行 Ping 操作的方法,可以传入目标 IP 地址或域名、PingReply 结构体和超时时间。PingCore: Ping 核心方法,用于发送 ICMP 数据包,计算往返时间等。CalCheckSum: 计算检验和的方法。GetTickCountCalibrate: 获取时钟计时器的校准值。
MyPing实现
1. CPing 构造函数和析构函数
CPing::CPing() : m_szICMPData(NULL), m_bIsInitSucc(FALSE)
{
// ...(省略其他初始化代码)
m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader));
if (m_szICMPData == NULL)
{
m_bIsInitSucc = FALSE;
}
}
CPing::~CPing()
{
WSACleanup();
if (NULL != m_szICMPData)
{
free(m_szICMPData);
m_szICMPData = NULL;
}
}
构造函数中,首先进行 Winsock 初始化,创建原始套接字,并分配内存用于存储 ICMP 数据。如果分配内存失败,则初始化标志 m_bIsInitSucc 置为 FALSE。析构函数负责清理 Winsock 资源和释放内存。
2. PingCore 函数
BOOL CPing::PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
// ...(省略其他代码)
if (!m_bIsInitSucc)
{
return FALSE;
}
// ...(省略其他代码)
if (sendto(m_sockRaw, m_szICMPData, nICMPDataSize, 0, (struct sockaddr*)&sockaddrDest, nSockaddrDestSize) == SOCKET_ERROR)
{
return FALSE;
}
// ...(省略其他代码)
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)
{
// ...(省略其他代码)
if (nPacketSize != SOCKET_ERROR)
{
IPHeader *pIPHeader = (IPHeader*)recvbuf;
USHORT usIPHeaderLen = (USHORT)((pIPHeader->m_byVerHLen & 0x0f) * 4);
ICMPHeader *pICMPHeader = (ICMPHeader*)(recvbuf + usIPHeaderLen);
if (pICMPHeader->m_usID == m_usCurrentProcID && pICMPHeader->m_byType == ECHO_REPLY && pICMPHeader->m_usSeq == usSeq)
{
// ...(省略其他代码)
return TRUE;
}
}
}
}
// ...(省略其他代码)
if (GetTickCountCalibrate() - ulSendTimestamp >= dwTimeout)
{
return FALSE;
}
}
}
PingCore 函数是 Ping 工具的核心部分,负责构建 ICMP 报文、发送报文、接收响应报文,并进行超时处理。通过循环等待接收事件,实时检测是否有 ICMP 响应报文到达。在接收到响应后,判断响应是否符合预期条件,如果符合则填充 pPingReply 结构体,并返回 TRUE。
3. CalCheckSum 函数
USHORT CPing::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);
}
CalCheckSum 函数用于计算 ICMP 报文的校验和。校验和的计算采用了累加和的方法,最后对累加和进行溢出处理。计算完成后,返回取反后的校验和。
4. GetTickCountCalibrate 函数
ULONG CPing::GetTickCountCalibrate()
{
// ...(省略其他代码)
return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS - s_ullFirstCallTickMS);
}
GetTickCountCalibrate 函数用于获取经过调校的系统时间。通过计算系统时间相对于 Ping 工具启动时的时间差,实现对系统时间的校准。这样做是为了处理系统时间溢出的情况。
5. Ping 函数
BOOL CPing::Ping(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
return PingCore(dwDestIP, pPingReply, dwTimeout);
}
BOOL CPing::Ping(char *szDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
if (NULL != szDestIP)
{
return PingCore(inet_addr(szDestIP), pPingReply, dwTimeout);
}
return FALSE;
}
Ping 函数是对 PingCore 函数的封装,根据目标 IP 地址调用 PingCore 进行 Ping
最后的MyPing.cpp完整实现如下所示;
#include "MyPing.h"
USHORT CPing::s_usPacketSeq = 0;
// 构造函数
CPing::CPing() :m_szICMPData(NULL), m_bIsInitSucc(FALSE)
{
WSADATA WSAData;
if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0)
{
// 如果初始化不成功则返回
return;
}
m_event = WSACreateEvent();
m_usCurrentProcID = (USHORT)GetCurrentProcessId();
m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0);
if (m_sockRaw == INVALID_SOCKET)
{
// 10013 以一种访问权限不允许的方式做了一个访问套接字的尝试
return;
}
else
{
WSAEventSelect(m_sockRaw, m_event, FD_READ);
m_bIsInitSucc = TRUE;
m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader));
if (m_szICMPData == NULL)
{
m_bIsInitSucc = FALSE;
}
}
}
// 析构函数
CPing::~CPing()
{
WSACleanup();
if (NULL != m_szICMPData)
{
free(m_szICMPData);
m_szICMPData = NULL;
}
}
// Ping 方法,传入目标 IP 地址或域名、PingReply 结构体和超时时间
BOOL CPing::Ping(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
return PingCore(dwDestIP, pPingReply, dwTimeout);
}
// Ping 方法,传入目标 IP 地址或域名、PingReply 结构体和超时时间
BOOL CPing::Ping(char *szDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
if (NULL != szDestIP)
{
return PingCore(inet_addr(szDestIP), pPingReply, dwTimeout);
}
return FALSE;
}
// Ping 核心方法,传入目标 IP 地址、PingReply 结构体和超时时间
BOOL CPing::PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
// 判断初始化是否成功
if (!m_bIsInitSucc)
{
return FALSE;
}
// 配置 SOCKET
sockaddr_in sockaddrDest;
sockaddrDest.sin_family = AF_INET;
sockaddrDest.sin_addr.s_addr = dwDestIP;
int nSockaddrDestSize = sizeof(sockaddrDest);
// 构建 ICMP 包
int nICMPDataSize = DEF_PACKET_SIZE + sizeof(ICMPHeader);
ULONG ulSendTimestamp = GetTickCountCalibrate();
USHORT usSeq = ++s_usPacketSeq;
memset(m_szICMPData, 0, nICMPDataSize);
ICMPHeader *pICMPHeader = (ICMPHeader*)m_szICMPData;
pICMPHeader->m_byType = ECHO_REQUEST;
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)
{
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);
if (nPacketSize != SOCKET_ERROR)
{
IPHeader *pIPHeader = (IPHeader*)recvbuf;
USHORT usIPHeaderLen = (USHORT)((pIPHeader->m_byVerHLen & 0x0f) * 4);
ICMPHeader *pICMPHeader = (ICMPHeader*)(recvbuf + usIPHeaderLen);
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);
pPingReply->m_dwTTL = pIPHeader->m_byTTL;
return TRUE;
}
}
}
}
// 超时
if (GetTickCountCalibrate() - ulSendTimestamp >= dwTimeout)
{
return FALSE;
}
}
}
// 计算检验和的方法
USHORT CPing::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);
}
// 获取时钟计时器的校准值
ULONG CPing::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);
}
如何使用
在主程序中直接引入头文件MyPing.h,并在main()函数中直接调用CPing类即可实现探测主机是否存活。
探测主机是否存活
#include "MyPing.h"
#include <iostream>
// 探测主机是否存活
bool TestPing(char *szIP)
{
CPing objPing;
PingReply reply;
objPing.Ping(szIP, &reply);
if (reply.m_dwTTL >= 10 && reply.m_dwTTL <= 255)
{
return true;
}
return false;
}
int main(int argc, char *argv[])
{
bool is_open = TestPing("202.89.233.100");
std::cout << "本机是否存活: " << is_open << std::endl;
system("pause");
return 0;
}
运行效果如下所示;

模拟系统Ping测试
#include "MyPing.h"
#include <iostream>
// 模拟系统Ping测试
void SystemPing(char *szIP, int szCount)
{
CPing objPing;
PingReply reply;
for (int x = 0; x < szCount; x++)
{
objPing.Ping(szIP, &reply);
std::cout << "探测主机: " << szIP << " 默认字节: " << DEF_PACKET_SIZE << " 发送长度: " << reply.m_dwBytes << " 时间: " << reply.m_dwRoundTripTime << " TTL: " << reply.m_dwTTL << std::endl;
Sleep(1000);
}
}
int main(int argc, char *argv[])
{
SystemPing("202.89.233.100", 5);
system("pause");
return 0;
}
运行效果如下所示;

参考资料
代码的实现来源于博客园Snser博主,此处仅用于功能收录以便于后期在项目中应用。
C++ 实现的Ping类的封装的更多相关文章
- iOS开发--QQ音乐练习,旋转动画的实现,音乐工具类的封装,定时器的使用技巧,SliderBar的事件处理
一.旋转动画的实现 二.音乐工具类的封装 -- 返回所有歌曲,返回当前播放歌曲,设置当前播放歌曲,返回下一首歌曲,返回上一首歌曲方法的实现 头文件 .m文件 #import "ChaosMu ...
- Java—类的封装、继承与多态
一.类和对象 1.类 类是数据以及对数据的一组操作的封装体. 类声明的格式: 类声明 { 成员变量的声明: 成员方法的声明及实现: } 1.1 声明类 [修饰符] class 类<泛型> ...
- 第三篇 :微信公众平台开发实战Java版之请求消息,响应消息以及事件消息类的封装
微信服务器和第三方服务器之间究竟是通过什么方式进行对话的? 下面,我们先看下图: 其实我们可以简单的理解: (1)首先,用户向微信服务器发送消息: (2)微信服务器接收到用户的消息处理之后,通过开发者 ...
- 025医疗项目-模块二:药品目录的导入导出-HSSF导入类的封装
上一篇文章提过,HSSF的用户模式会导致读取海量数据时很慢,所以我们采用的是事件驱动模式.这个模式类似于xml的sax解析.需要实现一个接口,HSSFListener接口. 原理:根据excel底层存 ...
- 022医疗项目-模块二:药品目录的导入导出-对XSSF导出excel类进行封装
资源全部来源于传智播客. 好的架构师写的程序,就算给刚入门的新手看,新手一看就知道怎么去用.所以我们要对XSSF导出excel类进行封装.这是架构师的工作,但我们也要知道. 我们写一个封装类: 这个类 ...
- 黑马程序员——JAVA基础之简述 类的封装
------- android培训.java培训.期待与您交流! ---------- 类的封装(Encapsulation) 封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式. 封装优 ...
- 【面试题001-补充】C++ MyString类的封装
[面试题001-补充]C++ MyString类的封装 一,C++ MyString类的封装 String.h: 123456789101112131415161718192021222324252 ...
- java中关于类的封装与继承,this、super关键字的使用
原创作品,可以转载,但是请标注出处地址http://www.cnblogs.com/V1haoge/p/5454849.html. this关键字: this代表当前对象,它有以下几种用途: 1.本类 ...
- Java---对象与类的封装
一.类和对象: package cn.hncu.Myclasslearn; /** * * @author hncu_chx * * Mylove amin */ /**类是一种数据类型,声明一个类就 ...
- php函数、类和对象以及类的封装、继承、类的静态方法、静态属性
1.函数 php内置函数可以直接使用,如果没有安装php扩展即可 自定义函数 //函数function 函数名 function dump($var = null){ //支出默认参数 ...
随机推荐
- 鸿蒙HarmonyOS实战-ArkTS语言(基本语法)
一.ArkTS语言基本语法 1.简介 HarmonyOS的ArkTS语言是一种基于TypeScript开发的语言,它专为HarmonyOS系统开发而设计.ArkTS语言结合了JavaScript的灵活 ...
- 微软的一些公开课,Python、机器学习、SQL、AI,全部免费
大家好,我是老章,刷X看到一位博主Alif Hossain@alifcoder总结了微软的一些公开课,全部免费,蛮不错的.感兴趣可以学一波,还能领徽章. 1. 机器学习简介 本课程是学习机器学习基础知 ...
- 正确使用 HttpClient
正确使用 HttpClient 其实标题应该叫:在控制台程序中使用IHttpClientFactory 以前一直使用的是HttpWebRequest,.NET6工程代码提示已过时,使用HttpClie ...
- 2013年 第四届蓝桥杯C/C++ B组(省赛)
第一题:高斯日记 大数学家高斯有个好习惯:无论如何都要记日记. 他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210 后来人们知道,那个整数就是日期,它表示那一天是高斯出生 ...
- C# 加解密
1. Md5 /// <summary> /// 不可逆加密 /// 1 防止被篡改 /// 2 防止明文存储 /// 3 防止抵赖,数字签名 /// </summary> ...
- 15-Verilog Coding Style
Verilog Coding Style 1.为什么需要Coding Style 可综合性 - 代码需要综合成网表,如果写了一些不可综合的代码,会出现错误 可读性,代码通常有多个版本,所以需要保证代码 ...
- 【Git】用法小记
解决windows环境下的CRLF与unix环境下的LF问题,windows提交时CRLF=>LF,签出时LF=>CRLF,unix环境保留 git config --global cor ...
- java - 字符串转数字
Integer.valueOf("str").intValue(): Integer.valueOf("123").intValue():
- [转帖]Oracle中unicode的几种不同字符编码模式
https://zhuanlan.zhihu.com/p/668340691# 在Oracle中unicode字符集中,存在以下几种不同unicode字符集的编码模式 AL32UTF8 UTF8 ...
- [转帖]kafka-console-ui v1.0.6发布
前言 kafka-console-ui 是一款web版的kafka管理平台,从第一次发布到现在已经两年了,断断续续也更新了7个版本了(v1.0.0~v1.0.6). 一些常用的功能也陆续完善了不少,相 ...