Windows平台纯C++实现的命令行Ping工具(含ICMP报文构造、校验和计算与完整课程报告)
2026/6/11 7:48:36 网站建设 项目流程

本文还有配套的精品资源,点击获取

简介:一个开箱即用的Windows命令行Ping程序,用标准C++编写,不依赖第三方库,通过原始套接字直接构造并发送ICMP Echo Request数据包,接收并解析Echo Reply响应,准确计算往返时间(RTT)、提取TTL值,并支持超时控制与重传判断。代码主体为ping.cpp,已适配Visual Studio环境,可一键编译运行;支持命令行参数配置目标IP地址、发送次数、超时毫秒数等常用选项;内置完整错误处理与Winsock初始化/清理逻辑。配套文档ping程序的实现.doc是完整的课程设计报告,涵盖ICMP协议帧格式详解、IPv4头部与ICMP头部的手动填充方法、16位反码校验和的分步计算过程、Raw Socket在Windows下的启用条件(需管理员权限)、常见失败原因(如WSAEPERM错误)及调试建议。所有源文件结构清晰,注释充分,适合网络编程入门实践、计算机网络实验或课程设计参考。

1. 项目概述:为什么一个“自己写的Ping”比调用系统命令更有价值?

你有没有试过在Windows命令行里敲下ping www.baidu.com,看着那一行行跳动的“来自 180.101.49.12 的回复”,心里突然冒出个念头:这背后到底发生了什么?不是调用系统自带的ping.exe,而是从零开始,亲手把那个小小的ICMP Echo Request数据包塞进网卡、发出去,再等它绕一圈回来,亲手拆开回复包、算出毫秒数、读出TTL值——这种掌控感,是任何现成工具都无法替代的。我第一次在Visual Studio里跑通自己写的ping.cpp时,盯着控制台输出的“Reply from 192.168.1.1: bytes=32 time<1ms TTL=64”,手心全是汗。这不是在写一个工具,是在和TCP/IP协议栈握手。

这个项目的核心关键词是ICMP Ping实现、原始套接字、C++网络编程,它不是一个玩具Demo,而是一套可直接编译、调试、复现的完整实践闭环。它不依赖libpcap、不调用WinPcap或Npcap,也不用Boost.Asio这类高级封装——所有逻辑都扎根于Windows原生Winsock2 API,用标准C++(C++11及以上)写就。这意味着你看到的每一行代码,都是网络协议最底层的真实映射:IPv4头部怎么填、ICMP类型码怎么设、校验和为什么必须用16位反码、为什么sendto()之前必须WSAStartup()、为什么普通用户权限下socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)会直接返回WSAEPERM错误。这些细节,在系统ping命令的黑盒里永远看不到,但在你的代码里,它们就是变量、结构体和几行循环。

它适合谁?如果你正在上《计算机网络》课,老师布置了“用原始套接字实现Ping”的实验,这份代码就是你的参考答案;如果你刚学完《操作系统》里的进程通信和I/O模型,想找个真实场景练手,这个项目能让你第一次真正理解“阻塞/非阻塞”、“超时等待”、“内核缓冲区”这些抽象概念;如果你是个自学网络编程的开发者,厌倦了网上那些只贴几行gethostbyname()就号称“会Socket”的教程,那这里从WSADATA初始化到closesocket()清理的每一步,都是你该踩的坑、该记的账。它不教你“如何快速上线”,它教你怎么把协议规范一页页翻译成C++代码——这才是网络编程的硬功夫。

2. 整体设计与思路拆解:为什么必须用Raw Socket?为什么不能用UDP/TCP?

2.1 协议层定位决定技术选型:ICMP不在传输层

很多人初学时有个误区:Ping是“网络工具”,所以应该用Socket编程,而Socket编程=TCP或UDP。这是根本性错位。我们得先拉一张协议栈简图在脑子里:

应用层 → 传输层(TCP/UDP) → 网络层(IP) → 数据链路层 → 物理层

ICMP(Internet Control Message Protocol)压根就不在传输层,它和IP协议平级,都属于网络层协议。它的报文是直接封装在IP数据报的数据部分里的,就像这样:

[以太网帧头] [IPv4头部] [ICMP Echo Request报文] [以太网帧尾]

而我们平时用的socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP),创建的是传输层套接字。操作系统内核会自动帮你填充IP头部(源IP、目的IP、TTL等),你只需要关心TCP三次握手或UDP的端口和数据。但ICMP没有端口号,它靠的是IP头部的“协议号”字段(值为1)来标识。要发送ICMP,你必须绕过传输层,直接构造完整的IP数据报——这就必须用原始套接字(Raw Socket),即socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)

提示:IPPROTO_ICMP这个参数很关键。它告诉内核:“我要发的是ICMP报文,请你别给我自动加TCP/UDP头,也别管端口,我就要往IP包里塞纯ICMP数据。” 如果你错写成IPPROTO_RAW,那内核连IP头部都不会帮你填,你得自己构造整个IP包(包括IP校验和),难度陡增三倍,且Windows对IPPROTO_RAW的支持更苛刻。本项目严格使用IPPROTO_ICMP,让内核代劳IP头部填充,聚焦ICMP本身。

2.2 Windows平台的特殊约束:管理员权限与防火墙穿透

在Linux下,创建Raw Socket通常只需sudo;但在Windows,这事更“严肃”。从Windows XP SP2开始,出于安全考虑,微软默认禁止非管理员用户创建Raw Socket。当你在VS里运行程序却收到WSAEPERM (10013)错误时,99%是因为没用管理员身份启动。这不是代码bug,是系统策略。解决方案只有两个:要么右键VS快捷方式→“以管理员身份运行”,要么在项目属性里勾选“清单工具→输入和输出→启用UAC执行级别→requireAdministrator”。

另一个隐形杀手是防火墙。很多企业环境或安全软件会默认拦截ICMP Echo Request。你代码逻辑完美,sendto()返回0,但就是收不到recvfrom()的响应——这时别急着改代码,先打开Windows Defender防火墙→“高级设置”→“入站规则”,找到“文件和打印机共享(回显请求 - ICMPv4-In)”并启用它。或者更简单:在命令行里临时执行netsh advfirewall firewall add rule name="Allow ICMP" dir=in action=allow protocol=icmpv4。记住,网络编程调试的第一步,永远是排除环境干扰,而不是怀疑自己的校验和算法。

2.3 架构分层:三层分离,各司其职

整个ping.cpp的代码结构,我刻意按“协议解析→报文构造→网络交互”三层组织,避免一锅炖:

  • 协议解析层(Protocol Layer):定义ICMPHeaderIPHeader等结构体,严格按RFC 792和RFC 791的字节序(大端)和字段偏移排布。比如ICMP的checksum字段必须是uint16_t且放在第2-3字节,identifiersequence必须是uint16_t且紧随其后。这里不用#pragma pack(1),而是用__declspec(align(1))确保结构体无填充,因为网络字节序要求内存布局必须和协议规范完全一致。

  • 报文构造层(Packet Builder):核心函数BuildICMPPacket()。它不负责发送,只做三件事:1)清零整个缓冲区;2)填充ICMP固定字段(Type=8, Code=0, ID, Seq);3)调用CalculateChecksum()计算校验和并填入。这个函数是纯内存操作,无任何系统调用,可单元测试——你可以传入一个已知ID/Seq的包,断言其校验和是否等于预期值。

  • 网络交互层(Network Loop):主循环PingLoop()。它管理Winsock生命周期、处理超时逻辑(select()+timeval)、重传计数、RTT计算(GetTickCount64()高精度计时)、TTL提取(从IP头部第9字节读取)。这一层和操作系统深度耦合,也是最容易出错的地方:比如recvfrom()返回的缓冲区长度可能小于IP包总长(因MTU限制),必须用IP_HDRINCL选项或检查IPHeader->total_length字段来判断是否截断。

这种分层不是为了炫技,而是为了可维护性。当你的Ping在某个特定路由器后超时,你可以单独#define DEBUG_PACKET,把构造好的ICMP包十六进制打印出来,和Wireshark抓的包逐字节比对;当RTT计算不准,你只需盯住GetTickCount64()前后两行,不用翻遍整个文件。

3. 核心细节解析与实操要点:从结构体定义到校验和的“反码”本质

3.1 结构体定义:字节对齐是生死线

网络协议对字节序和字段位置有严苛要求。一个常见的坑是:你在结构体里定义uint8_t type; uint8_t code; uint16_t checksum;,编译器可能为了性能在code后面插入1字节padding,导致checksum实际偏移不是2,而是4。这样构造出来的包,路由器一看就丢弃。解决方案是强制1字节对齐:

#pragma pack(push, 1) struct ICMPHeader { uint8_t type; // 8 for Echo Request uint8_t code; // 0 uint16_t checksum; // must be calculated uint16_t identifier; // our process ID uint16_t sequence; // increment per packet // optional data follows... }; #pragma pack(pop)

#pragma pack(1)告诉编译器:“每个成员都从上一个成员结束的下一个字节开始,不插空。” 这是Windows平台下最稳妥的方式。Linux下可用__attribute__((packed)),但本项目专注Windows,故统一用#pragma。注意#pragma pack(push, 1)pop必须成对出现,否则会影响后续所有结构体定义,引发难以追踪的内存越界。

3.2 校验和计算:为什么是“16位反码和”?它到底在防什么?

ICMP校验和的计算方法常被简化为“把所有16位字相加,取反码”。但这只是表象。它的本质是检测数据在传输过程中是否发生比特翻转(bit flip)。原理如下:

  1. 发送方将ICMP报文(包括伪头部,本项目因用IPPROTO_ICMP由内核填充IP头,故伪头部可省略)视为一系列16位整数(高位在前,即大端)。
  2. 将所有16位字相加,若最高位有进位,则将进位加到最低位(称为“回卷”,wrap-around)。
  3. 对最终和取按位取反(16位反码),得到校验和,填入checksum字段。
  4. 接收方做同样计算:将整个ICMP报文(含校验和字段)作为16位字相加,如果结果是0xFFFF(即全1),则校验通过。

关键点在于:校验和字段本身也参与计算。发送方填入的是“反码”,接收方计算时把它当普通数据加进去,理想情况下,所有正确数据的和 + “反码” = 0xFFFF,再加1溢出为0x0000。这就是为什么叫“反码校验和”。

本项目中的CalculateChecksum()函数,我写了详细注释说明每一步:

uint16_t CalculateChecksum(const uint8_t* data, size_t length) { uint32_t sum = 0; const uint16_t* word = reinterpret_cast<const uint16_t*>(data); // Step 1: Sum all 16-bit words while (length > 1) { sum += *word++; length -= 2; } // Step 2: Add remaining byte if length is odd if (length == 1) { sum += *(const uint8_t*)word; // treat as uint8_t } // Step 3: Fold 32-bit sum to 16 bits (wrap around) while (sum >> 16) { sum = (sum & 0xFFFF) + (sum >> 16); } // Step 4: Take one's complement return static_cast<uint16_t>(~sum); }

注意:while (sum >> 16)这个循环是精髓。它确保即使sum是0x12345(远大于16位),也能通过反复“回卷”压缩成16位。我曾见过有人用sum &= 0xFFFF代替,这是错的——它丢弃了高位,没做回卷,校验和必然错误。

3.3 Winsock初始化与清理:为什么WSAStartup()的版本号不能乱写?

WSAStartup()的第一个参数是MAKEWORD(2,2),表示请求Winsock 2.2版本。这个数字不能随便写成MAKEWORD(1,1)MAKEWORD(3,0)。原因在于:Windows不同版本支持的Winsock版本不同。Win10全面支持2.2,但某些嵌入式Windows CE可能只支持1.1。WSAStartup()会返回实际加载的版本号,你必须检查:

WSADATA wsaData; int result = WSAStartup(MAKEWORD(2,2), &wsaData); if (result != 0) { printf("WSAStartup failed: %d\n", result); return 1; } // 必须验证版本! if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { printf("Winsock version mismatch. Expected 2.2, got %d.%d\n", LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion)); WSACleanup(); return 1; }

LOBYTE取低字节(主版本号),HIBYTE取高字节(次版本号)。如果系统只支持2.1,wsaData.wVersion就是0x0102,LOBYTE=2, HIBYTE=1,此时你的程序应优雅退出,而不是强行运行——因为2.2新增的API(如WSAIoctl()的某些选项)在2.1下不存在,会导致未定义行为。

4. 实操过程与核心环节实现:从编译到抓包的全流程复现

4.1 Visual Studio环境配置:零依赖,一键构建

本项目在Visual Studio 2019/2022社区版上全程验证。无需安装任何第三方库,只需确保:

  1. 工作负载已安装:“使用C++的桌面开发”工作负载必须勾选。它包含Windows SDK和CMake工具链。
  2. 项目属性设置
    - C/C++ → 语言 → C++语言标准:ISO C++17标准(或更高,C++11足够)
    - 链接器 → 输入 → 附加依赖项:ws2_32.lib(这是Winsock2的核心库,必须显式链接)
    - C/C++ → 预处理器 → 预处理器定义:添加WIN32_LEAN_AND_MEAN(排除winsock1.h,避免与ws2_32.lib冲突)

创建空项目后,将ping.cpp拖入源文件,右键项目→“设为启动项目”,然后Ctrl+F5即可运行。首次运行若提示权限错误,请务必右键VS图标→“以管理员身份运行”,否则socket()必败。

4.2 命令行参数解析:灵活控制,贴近真实Ping体验

ping.cpp支持三个核心参数,完全对标系统ping命令:

ping.exe <target_ip> [-n count] [-w timeout_ms]
  • <target_ip>:必需,可以是IP地址(如192.168.1.1)或域名(如www.google.com)。域名解析由getaddrinfo()完成,它比老旧的gethostbyname()更现代、支持IPv6(虽然本项目只用IPv4)。
  • -n count:可选,指定发送次数,默认4次。这让你能快速测试连续性,比如ping.exe 127.0.0.1 -n 10发10个包看丢包率。
  • -w timeout_ms:可选,指定每次recvfrom()等待响应的毫秒数,默认1000(1秒)。设为500可加速失败判定,设为5000可容忍高延迟网络。

参数解析用标准argc/argv循环,简洁可靠:

int sendCount = 4; int timeoutMs = 1000; std::string targetIP; for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) { sendCount = atoi(argv[++i]); } else if (strcmp(argv[i], "-w") == 0 && i + 1 < argc) { timeoutMs = atoi(argv[++i]); } else if (targetIP.empty()) { targetIP = argv[i]; } }

实操心得:atoi()不检查输入合法性,生产环境应改用std::stoi()并捕获std::invalid_argument异常。但课程设计中,我们假设用户输入合理,优先保证逻辑清晰。

4.3 RTT计算与TTL提取:毫秒级精度与IP头部的“藏宝图”

往返时间(RTT)是Ping的灵魂指标。系统ping显示time<1ms,这“<1ms”是怎么来的?关键在于计时函数的选择:

  • clock():精度低,通常只有10-15ms,无法测出局域网内亚毫秒级延迟。
  • GetTickCount():精度约10-16ms,且会溢出(49.7天归零)。
  • GetTickCount64():64位无溢出,精度同GetTickCount(),但本项目用它已足够。
  • 最佳选择:QueryPerformanceCounter()+QueryPerformanceFrequency():提供微秒级精度,但代码稍复杂。本项目为平衡教学性与实用性,采用GetTickCount64(),并在注释中说明升级路径。

RTT计算逻辑在PingLoop()内:

uint64_t startTime = GetTickCount64(); int sent = sendto(sock, buffer, packetSize, 0, (sockaddr*)&destAddr, sizeof(destAddr)); if (sent == SOCKET_ERROR) { /* error handling */ } // 设置select超时 timeval tv = { timeoutMs / 1000, (timeoutMs % 1000) * 1000 }; fd_set readfds; FD_ZERO(&readfds); FD_SET(sock, &readfds); int activity = select(0, &readfds, nullptr, nullptr, &tv); if (activity > 0 && FD_ISSET(sock, &readfds)) { int recvLen = recvfrom(sock, recvBuffer, sizeof(recvBuffer), 0, (sockaddr*)&fromAddr, &fromAddrLen); if (recvLen >= sizeof(IPHeader) + sizeof(ICMPHeader)) { uint64_t endTime = GetTickCount64(); uint64_t rttMs = endTime - startTime; // Extract TTL from IP header (byte 8, 0-indexed) uint8_t ttl = static_cast<uint8_t>(recvBuffer[8]); printf("Reply from %s: bytes=%d time=%llums TTL=%d\n", inet_ntoa(((sockaddr_in*)&fromAddr)->sin_addr), recvLen - sizeof(IPHeader), rttMs, ttl); } }

TTL(Time To Live)值就藏在IP头部的第9个字节(索引8)。RFC 791规定,IP头部前20字节固定,其中字节8(0-indexed)就是TTL字段。recvBuffer[8]直接取值,无需解析整个IP头——这是对协议规范的精准利用。

4.4 完整抓包验证:用Wireshark亲眼见证你的ICMP包

理论终需实践检验。运行你的ping.exe 127.0.0.1 -n 1,同时打开Wireshark,过滤器输入icmp && ip.src == 127.0.0.1,你会看到:

  • 第一行:Echo (ping) request id=0x1234, seq=0x0001
  • 第二行:Echo (ping) reply id=0x1234, seq=0x0001

双击请求包,展开“Internet Protocol Version 4”,确认:
-Source: 127.0.0.1
-Destination: 127.0.0.1
-Protocol: ICMP (1)
-Time to live: 128(Windows默认TTL)

再展开“Internet Control Message Protocol”,确认:
-Type: 8 (Echo (ping) request)
-Code: 0
-Checksum: 0xXXXX(与你代码中CalculateChecksum()输出一致)
-Identifier: 0x1234(你的进程ID)
-Sequence number: 1

如果Wireshark显示Checksum: 0x0000 [should be 0xXXXX (maybe caused by checksum offload)],别慌——这是网卡硬件校验和卸载(Checksum Offload)导致的。Wireshark在驱动层抓包时,校验和还没被网卡计算,所以显示0。解决方案:在Wireshark中禁用“Checksum validation”(编辑→首选项→Protocols→IPv4→取消勾选“Validate the IPv4 checksum if possible”),或直接在设备管理器中禁用网卡的“IPv4 Checksum Offload”选项。这恰恰证明了你的代码是正确的:校验和计算逻辑无误,只是被硬件优化了。

5. 常见问题与排查技巧实录:那些让我熬夜到三点的坑

5.1 经典错误代码速查表

错误代码十进制含义最可能原因一句话解决
WSAENOTSOCK10038操作对象不是套接字socket()返回INVALID_SOCKET后,仍对它调用sendto()socket()后立即检查返回值,INVALID_SOCKETgoto cleanup
WSAEPERM10013权限不足程序未以管理员身份运行右键VS→“以管理员身份运行”,或修改程序清单
WSAEADDRNOTAVAIL10049无法指定被请求的地址bind()时指定了不存在的本地IP本项目不bind(),直接sendto(),此错误可忽略
WSAETIMEDOUT10060连接超时connect()超时,但本项目用sendto()不适用recvfrom()超时是正常现象,检查select()返回值而非此错误码
WSAECONNREFUSED10061连接被拒绝TCP/UDP端口无服务监听,但ICMP无端口概念此错误不会出现在ICMP Raw Socket中,出现说明你误用了SOCK_STREAM

注意:WSAETIMEDOUTrecvfrom()在阻塞模式下的超时表现,但本项目用select()实现非阻塞等待,所以recvfrom()本身不会返回此错误。如果看到它,说明你删掉了select()逻辑,直接调用recvfrom()且未设超时——这是重大逻辑错误。

5.2 调试技巧:从“为什么没反应”到“原来在这里”

技巧1:日志分级,精准定位
PingLoop()开头加printf("DEBUG: Sending packet #%d to %s...\n", seq, targetIP.c_str());,在recvfrom()后加printf("DEBUG: Received %d bytes from %s\n", recvLen, inet_ntoa(...));。当程序卡住,第一眼就能看到是卡在发送前,还是卡在接收后。比盲目加断点高效十倍。

技巧2:数据包十六进制快照
BuildICMPPacket()末尾,添加调试输出:

#ifdef DEBUG_PACKET printf("DEBUG: ICMP Packet (first 16 bytes): "); for (int i = 0; i < 16 && i < packetSize; ++i) { printf("%02X ", buffer[i]); } printf("\n"); #endif

编译时定义DEBUG_PACKET宏(项目属性→C/C++→预处理器→预处理器定义),运行后你会看到类似DEBUG: ICMP Packet (first 16 bytes): 08 00 00 00 12 34 00 01 00 00 00 00 00 00 00 00。对照RFC 792:08 00是Type=8, Code=0;12 34是Identifier;00 01是Sequence。如果00 00出现在Checksum位置,说明CalculateChecksum()没被调用——立刻去检查函数调用顺序。

技巧3:权限验证自动化
main()开头插入:

#include <shellapi.h> // ... if (!IsUserAnAdmin()) { printf("Error: This program requires administrator privileges.\n"); printf("Please run Visual Studio as Administrator.\n"); MessageBoxA(nullptr, "Administrator privileges required!", "Ping Tool", MB_ICONERROR); return 1; }

IsUserAnAdmin()是Windows API,能程序化检测权限。比让用户反复试错更友好。

5.3 课程报告写作要点:如何把代码讲成故事

配套文档ping程序的实现.doc不是代码说明书,而是你的思维导图。我建议按此逻辑组织:

  • 引言:不写“随着互联网发展…”,直接说“当我第一次在Wireshark里看到ICMP Echo Request包,发现它只有8字节固定头,却承载了整个网络连通性的判断依据——这促使我动手实现一个最小可行Ping。”
  • 协议分析:画一张手绘风格的ICMP包结构图,用不同颜色标出Type/Code/Checksum字段,并在旁边批注“此处必须为0x0800,否则路由器直接丢弃”。
  • 关键代码段:不要贴全部代码,只截取CalculateChecksum()函数,逐行解释“为什么sum >> 16要循环?因为0x10000 + 0x0001 = 0x10001,高位1必须回卷到低位”。
  • 调试手记:记录一个真实失败案例,比如“2023-10-05,尝试ping公司内网打印机,始终超时。用netsh interface ipv4 show interfaces发现网卡MTU为1492,而我的包大小设为1024,理论上没问题。最终用Wireshark发现打印机只响应TTL>=64的包,而我的代码生成的IP头TTL默认为64,但某中间路由器将其减为63后丢弃。解决方案:在IPHeader中显式设置ttl = 128。”——这种细节,才是报告的灵魂。

6. 扩展思考与工程化演进:从课程设计到工业级工具

这个Ping程序,是网络编程的“Hello World”,但它的骨架足以支撑更复杂的网络诊断工具。我在实际工作中,基于此框架做过三次演进:

第一次演进:支持IPv6 Ping
只需将AF_INET换成AF_INET6sockaddr_in换成sockaddr_in6IPPROTO_ICMP换成IPPROTO_ICMPV6,并调整ICMPHeaderidentifiersequence的偏移(IPv6 ICMPv6头部略有不同)。最大的坑是getaddrinfo()返回的addrinfo结构体,ai_addr可能是IPv4或IPv6,必须用ai_family字段动态判断。这教会我:协议兼容性不是加个#ifdef就行,而是要深入地址族抽象。

第二次演进:并发多目标Ping
std::thread启动多个PingLoop()实例,每个线程Ping一个IP。但很快遇到select()fd_set大小限制(Windows默认64)。解决方案是改用WSAEventSelect()或更现代的IOCP(I/O Completion Ports)。这让我明白:课程设计的单线程模型,在真实服务器场景下必须重构为异步I/O模型。

第三次演进:集成到监控系统
将Ping结果(RTT、丢包率、TTL)通过HTTP POST发送到内部监控API。这时ping.cpp不再是独立程序,而是被封装为PingEngine类,提供Start()Stop()GetStats()接口。头文件PingEngine.h暴露干净API,.cpp文件隐藏所有Winsock细节。这完成了从脚本到库的蜕变——而这一切,都始于那个在VS里第一次跑通的ping.exe

所以,别小看这个“简单的Ping”。它是一把钥匙,打开了理解整个TCP/IP协议栈的大门。当你亲手填满每一个字节,计算出那个精确的校验和,看着自己的数据包在Wireshark里跳动,你就不再是一个调用API的程序员,而是一个能和网络对话的工程师。下次再看到time=24ms,你知道那不只是一个数字,那是你的代码穿越了物理介质、路由节点、防火墙,在毫秒间完成的一次庄严握手。

本文还有配套的精品资源,点击获取

简介:一个开箱即用的Windows命令行Ping程序,用标准C++编写,不依赖第三方库,通过原始套接字直接构造并发送ICMP Echo Request数据包,接收并解析Echo Reply响应,准确计算往返时间(RTT)、提取TTL值,并支持超时控制与重传判断。代码主体为ping.cpp,已适配Visual Studio环境,可一键编译运行;支持命令行参数配置目标IP地址、发送次数、超时毫秒数等常用选项;内置完整错误处理与Winsock初始化/清理逻辑。配套文档ping程序的实现.doc是完整的课程设计报告,涵盖ICMP协议帧格式详解、IPv4头部与ICMP头部的手动填充方法、16位反码校验和的分步计算过程、Raw Socket在Windows下的启用条件(需管理员权限)、常见失败原因(如WSAEPERM错误)及调试建议。所有源文件结构清晰,注释充分,适合网络编程入门实践、计算机网络实验或课程设计参考。


本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询