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 代码,实现一份代码跨平台运行。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐