Ping 使用 Internet 控制消息协议(ICMP)来测试主机之间的连接。当用户发送一个 ping 请求时,则对应的发送一个 ICMP Echo 请求消息到目标主机,并等待目标主机回复一个 ICMP Echo 回应消息。如果目标主机接收到请求并且网络连接正常,则会返回一个回应消息,表示主机之间的网络连接是正常的。如果目标主机没有收到请求消息或网络连接不正常,则不会有回应消息返回。

编译报错问题解决

Windows环境下编程不可避免的会用到windows.hwinsock.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 包的序列号
};

下面是对每个结构体成员的简要说明:

  1. 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地址。
  2. ICMPHeader 结构体:
    • m_byType: ICMP类型。
    • m_byCode: ICMP代码。
    • m_usChecksum: 检验和。
    • m_usID: 标识符。
    • m_usSeq: 序号。
    • m_ulTimeStamp: 时间戳(非标准ICMP头部)。
  3. 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类的封装的更多相关文章

  1. iOS开发--QQ音乐练习,旋转动画的实现,音乐工具类的封装,定时器的使用技巧,SliderBar的事件处理

    一.旋转动画的实现 二.音乐工具类的封装 -- 返回所有歌曲,返回当前播放歌曲,设置当前播放歌曲,返回下一首歌曲,返回上一首歌曲方法的实现 头文件 .m文件 #import "ChaosMu ...

  2. Java—类的封装、继承与多态

    一.类和对象 1.类 类是数据以及对数据的一组操作的封装体. 类声明的格式: 类声明 { 成员变量的声明: 成员方法的声明及实现: } 1.1 声明类 [修饰符] class 类<泛型> ...

  3. 第三篇 :微信公众平台开发实战Java版之请求消息,响应消息以及事件消息类的封装

    微信服务器和第三方服务器之间究竟是通过什么方式进行对话的? 下面,我们先看下图: 其实我们可以简单的理解: (1)首先,用户向微信服务器发送消息: (2)微信服务器接收到用户的消息处理之后,通过开发者 ...

  4. 025医疗项目-模块二:药品目录的导入导出-HSSF导入类的封装

    上一篇文章提过,HSSF的用户模式会导致读取海量数据时很慢,所以我们采用的是事件驱动模式.这个模式类似于xml的sax解析.需要实现一个接口,HSSFListener接口. 原理:根据excel底层存 ...

  5. 022医疗项目-模块二:药品目录的导入导出-对XSSF导出excel类进行封装

    资源全部来源于传智播客. 好的架构师写的程序,就算给刚入门的新手看,新手一看就知道怎么去用.所以我们要对XSSF导出excel类进行封装.这是架构师的工作,但我们也要知道. 我们写一个封装类: 这个类 ...

  6. 黑马程序员——JAVA基础之简述 类的封装

    ------- android培训.java培训.期待与您交流! ---------- 类的封装(Encapsulation)  封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式. 封装优 ...

  7. 【面试题001-补充】C++ MyString类的封装

    [面试题001-补充]C++ MyString类的封装  一,C++ MyString类的封装 String.h: 123456789101112131415161718192021222324252 ...

  8. java中关于类的封装与继承,this、super关键字的使用

    原创作品,可以转载,但是请标注出处地址http://www.cnblogs.com/V1haoge/p/5454849.html. this关键字: this代表当前对象,它有以下几种用途: 1.本类 ...

  9. Java---对象与类的封装

    一.类和对象: package cn.hncu.Myclasslearn; /** * * @author hncu_chx * * Mylove amin */ /**类是一种数据类型,声明一个类就 ...

  10. php函数、类和对象以及类的封装、继承、类的静态方法、静态属性

    1.函数     php内置函数可以直接使用,如果没有安装php扩展即可     自定义函数 //函数function 函数名 function dump($var = null){ //支出默认参数 ...

随机推荐

  1. 玩转Python:在Python中处理表格数据,几个非常流行且功能强大的库

    在Python中处理表格数据,有几个非常流行且功能强大的库.以下是一些最常用的库及其示例代码: 1. Pandas Pandas是一个开放源代码的.BSD许可的库,为Python编程语言提供高性能.易 ...

  2. Exception in thread "main" joptsimple.UnrecognizedOptionException: zookeeper is not a recognized option

    背景: 在kafka集群上使用topic相关的命令时,报错: Exception in thread "main" joptsimple.UnrecognizedOptionExc ...

  3. SE54视图簇

    一.创建关联表 头表 行表 设置行表的外键  创建两张表的表维护生成器,此处不再展开 二.SE54视图簇 激活上述 三.创建事务代码维护 四.效果展示 定期更文,欢迎关注 TRANSLATE with ...

  4. Dapper.Lite 使用教程

    以MySQL数据库为例 一. 安装 NuGet搜索Dapper.Lite并安装最新版本. NuGet搜索MySqlConnector并安装最新版本. 也可以使用MySql.Data库,但MySqlCo ...

  5. .NET使用QuestPDF高效地生成PDF文档

    前言 在.NET平台中操作生成PDF的类库有很多如常见的有iTextSharp.PDFsharp.Aspose.PDF等,今天我们分享一个用于生成PDF文档的现代开源.NET库:QuestPDF,本文 ...

  6. Spring | 利用Maven搭建Spring的开发环境

    本节主要介绍如何利用Maven搭建 Spring 开发环境,使用 Spring 之前需要安装 JDK .Maven和 IDEA 建议一定要从 Maven 项目开始,而不是从空项目开始,空项目开始会出现 ...

  7. L1-020 帅到没朋友 (20分)

    当芸芸众生忙着在朋友圈中发照片的时候,总有一些人因为太帅而没有朋友.本题就要求你找出那些帅到没有朋友的人. 输入格式: 输入第一行给出一个正整数N(≤100),是已知朋友圈的个数:随后N行,每行首先给 ...

  8. Canvas原生绘制树状结构拓扑图

    其实当前Web库实现Canvas绘制树状结构的组件很多,而且功能也很强大,但是难免有些场景无法实现需要自己开发,本文主要是提供一种思路 先附一个不错的拓扑图开发地址:https://www.zhihu ...

  9. freeswitch-1.10.7性能测试

    概述 freeswitch 是一款简单好用的开源软交换平台. freeswitch-1.10.7是比较新的版本,使用时间比较短,需要一个可参考的性能指标,作为实际使用过程中的配置指导. 环境 cent ...

  10. Linux 文件目录配置及命令总结

    前言 在Linux中,一切皆文件,而每一个文件和目录都是从根目录开始的 Linux文件目录的作用 /bin目录:用来存放二进制可执行命令的目录,用户常用的命令都存在该目录下. /sbin目录:用来存放 ...