Ping 命令
Ping是基于ICMP协议的网络连通性测试工具,用于检测设备间的连通性、延迟和丢包率。其工作原理是通过发送ICMP回声请求报文并接收响应报文来实现网络诊断。Ping属于网络层工具,不依赖端口,TTL值可反映路由跳数。虽然Ping能验证网络连通性,但无法检测应用层服务可用性。文章还提供了Linux和Windows环境下实现Ping工具的代码示例,包括原始套接字和系统API两种实现方式。最后指出Pin
Ping 是计算机网络中最基础、最常用的网络连通性测试工具,核心作用是基于 ICMP 协议检测两台网络设备之间的连通性、网络延迟和丢包率,是开发 / 运维排查网络问题的 “第一抓手”。
一、Ping 的基础原理
1. 为什么需要 Ping?
日常使用中,当你访问网站失败、远程连接设备不通时,第一个想到的就是ping 目标IP/域名—— 它能快速回答三个核心问题:
- 目标设备是否在线(连通性);
- 数据往返目标设备的时间(网络延迟);
- 有多少数据包丢失(丢包率);这些信息是排查网络问题的基础,比如延迟过高可能是路由拥堵,丢包率 100% 可能是目标设备关机或防火墙拦截。
2. Ping 的底层原理(基于 ICMP 协议)
Ping 本身不是协议,而是基于ICMP(互联网控制报文协议) 实现的应用程序,核心依赖 ICMP 的 “回声请求” 和 “回声响应” 报文:
核心报文类型
- ICMP 回声请求(Type=8,Code=0):Ping 发送端向目标设备发送的报文,请求对方回应;
- ICMP 回声响应(Type=0,Code=0):目标设备收到请求后,向发送端返回的响应报文。
Ping 的完整工作流程(以ping 192.168.1.1为例)
- 构造 ICMP 报文:发送端(如你的电脑)构造 ICMP 回声请求报文,填充标识符(区分不同 ping 进程)、序列号(区分不同报文)、时间戳(用于计算延迟),并计算校验和(确保报文完整性);
- 封装与转发:ICMP 报文被封装到 IP 报文中(IP 头包含源 IP、目标 IP、TTL 等),通过网卡发送到网络;
- 目标设备处理:目标设备(如路由器 192.168.1.1)接收 IP 报文,解封装出 ICMP 请求:
- 若目标设备在线且允许 ICMP 响应:构造 ICMP 回声响应报文,原路返回给发送端;
- 若目标设备离线 / 禁用 ICMP:无响应,发送端判定为 “超时”;
- 接收与计算:发送端收到响应报文后,匹配标识符和序列号,通过时间戳计算往返延迟(RTT),同时统计接收 / 发送报文数,计算丢包率。
3. Ping 的核心参数
执行ping 192.168.1.1后,终端输出的关键参数含义:
来自 192.168.1.1 的回复:字节=32 时间=1ms TTL=64
- 字节 = 32:ICMP 报文的数据部分长度(默认 32 字节,可自定义);
- 时间 = 1ms:往返延迟(RTT),越小说明网络越通畅;
- TTL=64:生存时间(Time To Live),IP 报文每经过一个路由器(跳数),TTL 减 1,减到 0 则报文被丢弃并返回 “超时”;不同系统默认 TTL 不同(Windows=128,Linux=64,macOS=60),可通过 TTL 值大致判断目标设备系统。
二、Ping 的注意点
1. Ping 基于什么协议?属于哪一层?有无端口?
基于 ICMP 协议(互联网控制报文协议),属于网络层;无独立端口(端口是传输层 TCP/UDP 的概念,ICMP 工作在网络层,无需端口)。
2. TTL 的作用是什么?Ping 中 TTL 异常说明什么?
TTL 核心作用是防止 IP 报文在网络中无限循环转发(如路由环路);异常场景:① TTL=0:报文在路由途中被丢弃,提示 “请求超时”,可能是路由跳数过多或目标网段不可达;② TTL 值远小于默认值(如 Windows ping 目标返回 TTL=64):说明目标是 Linux 设备,且报文经过了 128-64=64 跳(实际一般是局域网,TTL 未递减)。
3. Ping 的丢包率 100% 可能有哪些原因?
① 目标设备离线 / 断电;② 目标设备禁用了 ICMP 响应(防火墙 / 系统设置);③ 发送端与目标设备不在同一网段且无路由可达;④ 网络链路中断(如网线断开、WiFi 未连接)。
4. Ping 通目标设备,但无法访问其服务(如 80 端口的网页),原因是什么?
核心原因:Ping 仅检测 “网络连通性”,不检测 “应用层服务可用性”,常见场景:
- 目标设备的应用层服务未启动(如 Web 服务没开);
- 目标设备防火墙放行 ICMP,但拦截了对应端口(如 80/443);
- 服务绑定了特定 IP,而非通配 IP(如仅绑定 127.0.0.1,局域网无法访问)。
5. Ping 与 Traceroute(tracert)的核心区别?
| 维度 | Ping | Traceroute |
| 核心作用 | 检测端到端连通性、延迟、丢包率 | 定位报文从发送端到目标的路由节点 |
| 底层原理 | ICMP 回声请求 / 响应 | Linux:UDP 报文(目标端口不可达);Windows:ICMP 超时报文 |
| 输出结果 | 延迟、丢包率 | 每一跳路由的 IP、延迟 |
6. Ping 洪水攻击的原理及防御?
原理:攻击者发送大量 ICMP 回声请求报文,耗尽目标设备的带宽 / CPU 资源,导致正常服务不可用;防御:① 防火墙限制 ICMP 请求速率(如每秒最多接收 10 个);② 禁用非必要设备的 ICMP 响应;③ 开启网络设备的流量清洗功能。
7. Ping 是传输层工具?
网络层,基于 ICMP,无端口;
8. Ping 通一定能访问目标服务?
不一定,服务端口可能被拦截;
9. 所有设备都必须响应 Ping?
服务器 / 路由器可通过配置禁用 ICMP 响应,提升安全性;
10. Ping 的延迟等于实际业务延迟?
Ping 仅测 ICMP 报文延迟,业务(如 HTTP/MySQL)延迟还包含应用层处理时间,二者不一定一致。
三、自定义 Ping 工具实现
1. 环境与依赖
- Linux 环境:需 root 权限(创建原始套接字),无需额外库,依赖系统 socket API;
- Windows 环境:调用系统
ICMP.dll(自带,无需安装),无需 root / 管理员权限(部分场景需); - 核心思路:分别实现 Linux/Windows 版本,代码注释详细
2. 实战 1:Linux 下基于 Raw Socket 实现 Ping
Raw Socket(原始套接字)允许直接构造 ICMP 报文,贴近 ping 底层实现,代码如下:
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <errno.h>
// 计算ICMP报文校验和(ICMP必填,防止报文篡改)
unsigned short icmp_checksum(unsigned short* buf, int len) {
unsigned long sum = 0;
// 按16位累加
while (len > 1) {
sum += *buf++;
len -= 2;
}
// 处理奇数长度
if (len == 1) {
sum += *(unsigned char*)buf;
}
// 折叠高位
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return ~sum;
}
// 发送ICMP请求并接收响应
int ping_linux(const std::string& dest_ip, int count = 4, int timeout = 5000) {
// 1. 创建原始套接字(需root权限,SOCK_RAW表示原始套接字)
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd < 0) {
std::cerr << "创建套接字失败(需root权限):" << strerror(errno) << std::endl;
return -1;
}
// 2. 设置目标地址
struct sockaddr_in dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
if (inet_pton(AF_INET, dest_ip.c_str(), &dest_addr.sin_addr) <= 0) {
std::cerr << "无效的IP地址:" << dest_ip << std::endl;
close(sockfd);
return -1;
}
// 3. 构造ICMP报文(ICMP头+数据)
struct icmp icmp_msg;
memset(&icmp_msg, 0, sizeof(icmp_msg));
icmp_msg.icmp_type = ICMP_ECHO; // 回声请求(Type=8)
icmp_msg.icmp_code = 0; // Code=0
icmp_msg.icmp_id = getpid() & 0xFFFF; // 标识符:用进程ID区分不同ping
char data[32] = "C++_Ping_Test"; // 数据部分(默认32字节)
int icmp_len = sizeof(icmp_msg) + sizeof(data);
// 填充数据部分
memcpy((char*)&icmp_msg + sizeof(icmp_msg), data, sizeof(data));
// 4. 发送count次请求,统计结果
struct sockaddr_in src_addr;
socklen_t src_len = sizeof(src_addr);
char recv_buf[1024];
int send_count = 0, recv_count = 0;
std::cout << "正在Ping " << dest_ip << " 具有 " << sizeof(data) << " 字节的数据:" << std::endl;
for (int i = 0; i < count; i++) {
// 填充序列号(递增)
icmp_msg.icmp_seq = i;
// 先置0,再计算校验和
icmp_msg.icmp_cksum = 0;
icmp_msg.icmp_cksum = icmp_checksum((unsigned short*)&icmp_msg, icmp_len);
// 记录发送时间
struct timeval send_time;
gettimeofday(&send_time, nullptr);
// 发送ICMP报文
int send_len = sendto(sockfd, &icmp_msg, icmp_len, 0,
(struct sockaddr*)&dest_addr, sizeof(dest_addr));
if (send_len < 0) {
std::cerr << "第" << i+1 << "次发送失败:" << strerror(errno) << std::endl;
continue;
}
send_count++;
// 设置接收超时
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(sockfd, &read_fds);
struct timeval tv = {timeout / 1000, (timeout % 1000) * 1000};
int ret = select(sockfd + 1, &read_fds, nullptr, nullptr, &tv);
if (ret == 0) {
// 超时
std::cout << "来自 " << dest_ip << " 的请求超时。" << std::endl;
continue;
} else if (ret < 0) {
// 接收错误
std::cerr << "第" << i+1 << "次接收错误:" << strerror(errno) << std::endl;
continue;
}
// 读取响应报文
int recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0,
(struct sockaddr*)&src_addr, &src_len);
if (recv_len < 0) {
std::cerr << "第" << i+1 << "次读取响应失败:" << strerror(errno) << std::endl;
continue;
}
// 记录接收时间,计算延迟
struct timeval recv_time;
gettimeofday(&recv_time, nullptr);
double delay = (recv_time.tv_sec - send_time.tv_sec) * 1000.0 +
(recv_time.tv_usec - send_time.tv_usec) / 1000.0;
// 解析响应:跳过IP头(IP头长度=ip_hl*4)
struct ip* ip_header = (struct ip*)recv_buf;
int ip_header_len = ip_header->ip_hl * 4;
struct icmp* recv_icmp = (struct icmp*)(recv_buf + ip_header_len);
// 验证响应合法性:Type=0(响应)、ID匹配、序列号匹配
if (recv_icmp->icmp_type == ICMP_ECHOREPLY &&
recv_icmp->icmp_id == (getpid() & 0xFFFF) &&
recv_icmp->icmp_seq == i) {
std::cout << "来自 " << inet_ntoa(src_addr.sin_addr) << " 的回复:"
<< "字节=" << recv_len - ip_header_len
<< " 时间=" << delay << "ms"
<< " TTL=" << (int)ip_header->ip_ttl << std::endl;
recv_count++;
} else {
std::cout << "第" << i+1 << "次接收无效响应" << std::endl;
}
sleep(1); // 间隔1秒发送一次
}
// 输出统计结果
std::cout << "\n--- " << dest_ip << " Ping 统计信息 ---" << std::endl;
std::cout << send_count << " 个数据包已发送,"
<< recv_count << " 个数据包已接收,"
<< (send_count - recv_count) * 100 / send_count << "% 丢失," << std::endl;
close(sockfd);
return 0;
}
int main() {
std::string dest_ip;
std::cout << "请输入要Ping的IP地址:";
std::cin >> dest_ip;
ping_linux(dest_ip);
return 0;
}
编译与运行(Linux)
- 安装依赖(无需额外库,系统自带);
- 编译命令:
g++ linux_ping.cpp -o linux_ping; - 运行(需 root 权限):
sudo ./linux_ping,输入目标 IP 即可。
3. 实战 2:Windows 下基于 ICMP.dll 实现 Ping
Windows 下无需手动构造 ICMP 报文,调用系统ICMP.dll的封装 API 更简单,代码如下:
#include <iostream>
#include <string>
#include <windows.h>
#include <iphlpapi.h>
#include <icmpapi.h>
// 链接ICMP库(Windows自带)
#pragma comment(lib, "icmp.lib")
#pragma comment(lib, "iphlpapi.lib")
// Windows下Ping实现
int ping_windows(const std::string& dest_ip, int count = 4, int timeout = 5000) {
// 1. 创建ICMP句柄
HANDLE icmp_handle = IcmpCreateFile();
if (icmp_handle == INVALID_HANDLE_VALUE) {
std::cerr << "创建ICMP句柄失败,错误码:" << GetLastError() << std::endl;
return -1;
}
// 2. 准备数据和响应缓冲区
const int data_len = 32;
char data[data_len] = "C++_Ping_Test";
// 响应缓冲区大小:ICMP_ECHO_REPLY + 数据长度
DWORD reply_buf_size = sizeof(ICMP_ECHO_REPLY) + data_len;
char* reply_buf = new char[reply_buf_size];
if (reply_buf == nullptr) {
std::cerr << "内存分配失败" << std::endl;
IcmpCloseHandle(icmp_handle);
return -1;
}
// 3. 发送count次请求
int send_count = 0, recv_count = 0;
std::cout << "正在Ping " << dest_ip << " 具有 " << data_len << " 字节的数据:" << std::endl;
for (int i = 0; i < count; i++) {
// 发送ICMP请求(IcmpSendEcho是Windows封装的核心函数)
DWORD reply_count = IcmpSendEcho(
icmp_handle, // ICMP句柄
inet_addr(dest_ip.c_str()), // 目标IP(转换为网络字节序)
data, // 发送数据
data_len, // 数据长度
nullptr, // ICMP选项(默认)
reply_buf, // 响应缓冲区
reply_buf_size, // 缓冲区大小
timeout // 超时时间(ms)
);
send_count++;
if (reply_count == 0) {
// 无响应(超时/失败)
std::cout << "来自 " << dest_ip << " 的请求超时。" << std::endl;
Sleep(1000);
continue;
}
// 解析响应
ICMP_ECHO_REPLY* echo_reply = (ICMP_ECHO_REPLY*)reply_buf;
if (echo_reply->Status == IP_SUCCESS) {
std::cout << "来自 " << inet_ntoa(*(in_addr*)&echo_reply->Address) << " 的回复:"
<< "字节=" << data_len
<< " 时间=" << echo_reply->RoundTripTime << "ms"
<< " TTL=" << (int)echo_reply->Options.Ttl << std::endl;
recv_count++;
} else {
std::cout << "第" << i+1 << "次接收失败,状态码:" << echo_reply->Status << std::endl;
}
Sleep(1000); // 间隔1秒
}
// 输出统计结果
std::cout << "\n--- " << dest_ip << " Ping 统计信息 ---" << std::endl;
std::cout << send_count << " 个数据包已发送,"
<< recv_count << " 个数据包已接收,"
<< (send_count - recv_count) * 100 / send_count << "% 丢失," << std::endl;
// 释放资源
delete[] reply_buf;
IcmpCloseHandle(icmp_handle);
return 0;
}
int main() {
std::string dest_ip;
std::cout << "请输入要Ping的IP地址:";
std::cin >> dest_ip;
ping_windows(dest_ip);
return 0;
}
编译与运行(Windows)
- 环境:Visual Studio 2019/2022(其他版本兼容);
- 编译:创建 “控制台应用” 项目,粘贴代码,直接点击 “生成”;
- 运行:双击生成的 exe 文件,输入目标 IP 即可(无需管理员权限)。
4. 扩展方向
- 批量 Ping 工具:读取文本文件中的多个 IP / 域名,批量执行 Ping,生成包含延迟、丢包率的检测报告;
- 自定义参数:支持自定义数据长度、超时时间、Ping 次数、TTL 值;
- 域名解析 Ping:先解析域名(如
ping www.baidu.com)为 IP,再执行 Ping(结合之前的 DNS 实战代码); - 可视化 Ping:结合 Qt/UI 库,实现图形化界面,实时显示延迟曲线、丢包率;
- 跨平台封装:用条件编译(
#ifdef _WIN32)整合 Linux/Windows 代码,实现一份代码跨平台运行。
更多推荐



所有评论(0)