5天:网络命令入门 - ping

第5天:网络命令入门 - ping

“ping一下,网络通不通立刻知道。”

📚 今日目标

  • 理解ping命令的工作原理
  • 掌握ICMP协议基础知识
  • 学会使用ping命令进行网络诊断
  • 编写自己的ping工具

🎯 什么是ping?

生活中的例子

ping = 喊话游戏

你:喂!能听到吗? (发送)
朋友:能听到!         (回应)
你:太好了,通信正常! (确认)

ping命令就是用来测试网络连通性的工具,它会发送一个数据包到目标主机,然后等待回应。

ping的作用

  1. 测试连通性:目标主机是否可达
  2. 测量延迟:数据往返需要多少时间
  3. 检测丢包:有多少数据包丢失了
  4. 诊断网络问题:定位网络故障

📡 ICMP协议

什么是ICMP?

ICMP(Internet Control Message Protocol,互联网控制消息协议)是TCP/IP协议族的一部分,工作在网际层(IP层)。

ICMP的作用

ICMP = 网络的"通讯员"

主要任务:
1. 报告错误("对方不在家")
2. 诊断问题("ping测试")
3. 提供反馈("超时了")

常见的ICMP消息类型

类型名称用途
0Echo Replyping的回应
3Destination Unreachable目标不可达
5Redirect重定向
8Echo Requestping的请求
11Time Exceeded超时(TTL=0)

🔍 ping的工作原理

完整流程

1. 发送 ICMP Echo Request(类型8)
   你的电脑 → 目标主机
   "你好,我是192.168.1.100,请回应!"

2. 接收 ICMP Echo Reply(类型0)
   目标主机 → 你的电脑
   "收到!我是8.8.8.8,一切正常!"

3. 计算RTT(往返时间)
   发送时间:10:00:00.000
   接收时间:10:00:00.050
   RTT = 50毫秒

RTT(Round-Trip Time)

RTT:数据包从发送到收到回复的总时间

RTT = 去程时间 + 回程时间

影响RTT的因素:
- 物理距离(光速是有限的)
- 网络拥塞
- 路由器处理时间
- 对方服务器处理时间

ICMP报文结构

ICMP Echo Request/Reply 格式:

0               8              16              24              31
+---------------+---------------+---------------+---------------+
|     类型      |     代码      |           校验和              |
|   (Type)      |   (Code)      |         (Checksum)            |
+---------------+---------------+---------------+---------------+
|          标识符 (Identifier)   |      序列号 (Sequence)        |
+---------------+---------------+---------------+---------------+
|                         数据 (Data)                            |
|                         可选,用于填充                         |
+---------------+---------------+---------------+---------------+

字段说明:
- 类型:8=请求,0=回应
- 代码:通常为0
- 校验和:用于检测数据完整性
- 标识符:标识这个ping进程
- 序列号:标识这是第几个包
- 数据:可选的负载数据

🔧 使用ping命令

基本用法

Windows

# 基本ping
ping baidu.com

# ping 4次后停止
ping -n 4 baidu.com

# 指定包大小(字节)
ping -l 1000 baidu.com

# 持续ping
ping -t baidu.com

Linux/Mac

# 基本ping
ping baidu.com

# ping 4次后停止
ping -c 4 baidu.com

# 指定包大小(字节)
ping -s 1000 baidu.com

# 快速ping(间隔0.2秒)
ping -i 0.2 baidu.com

输出解析

$ ping baidu.com

PING baidu.com (220.181.38.148): 56 data bytes
64 bytes from 220.181.38.148: icmp_seq=0 ttl=52 time=28.5 ms
64 bytes from 220.181.38.148: icmp_seq=1 ttl=52 time=27.8 ms
64 bytes from 220.181.38.148: icmp_seq=2 ttl=52 time=28.1 ms
64 bytes from 220.181.38.148: icmp_seq=3 ttl=52 time=29.2 ms

--- baidu.com ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 27.8/28.4/29.2/0.5 ms

字段含义

  • 64 bytes:数据包大小
  • icmp_seq:序列号(第几个包)
  • ttl=52:生存时间(经过了64-52=12个路由器)
  • time=28.5 ms:RTT往返时间
  • 0.0% packet loss:丢包率
  • min/avg/max:最小/平均/最大RTT

🔧 实战项目:实现简易ping工具

让我们用Python实现一个简单的ping工具!

代码实现

# day05_simple_ping.py
# 功能:实现简单的ping工具

import socket
import struct
import time
import sys

def calculate_checksum(data):
    """
    计算ICMP校验和

    参数:
        data: 要计算校验和的数据(bytes)

    返回:
        校验和(int)

    工作原理:
        1. 将数据按16位分组求和
        2. 如果有进位,将进位加到结果上
        3. 取反码得到校验和
    """
    # 如果数据长度是奇数,补一个字节0
    if len(data) % 2 != 0:
        data += b'\x00'

    # 按16位(2字节)分组求和
    checksum = 0
    for i in range(0, len(data), 2):
        # 将两个字节组合成一个16位整数
        word = (data[i] << 8) + data[i + 1]
        checksum += word

    # 处理进位:将高16位加到低16位
    while checksum >> 16:
        checksum = (checksum & 0xFFFF) + (checksum >> 16)

    # 取反码
    checksum = ~checksum & 0xFFFF

    return checksum

def create_icmp_packet(packet_id, sequence):
    """
    创建ICMP Echo Request数据包

    参数:
        packet_id: 标识符(用于区分不同的ping进程)
        sequence: 序列号(标识第几个包)

    返回:
        完整的ICMP数据包(bytes)

    ICMP包结构:
        [类型(1字节) | 代码(1字节) | 校验和(2字节) |
         标识符(2字节) | 序列号(2字节) | 数据]
    """
    # ICMP类型:8=Echo Request
    icmp_type = 8
    # ICMP代码:0
    icmp_code = 0
    # 校验和:先设为0,稍后计算
    icmp_checksum = 0

    # 打包ICMP头部(不包括校验和)
    # ! = 网络字节序(大端)
    # B = unsigned char (1字节)
    # H = unsigned short (2字节)
    header = struct.pack('!BBHHH', icmp_type, icmp_code, icmp_checksum,
                         packet_id, sequence)

    # 数据部分:发送时间戳(用于计算RTT)
    data = struct.pack('!d', time.time())

    # 计算校验和
    icmp_checksum = calculate_checksum(header + data)

    # 重新打包,加入正确的校验和
    header = struct.pack('!BBHHH', icmp_type, icmp_code, icmp_checksum,
                         packet_id, sequence)

    return header + data

def parse_icmp_reply(packet):
    """
    解析ICMP Echo Reply数据包

    参数:
        packet: 接收到的数据包

    返回:
        (icmp_type, icmp_code, icmp_id, icmp_seq, send_time)
    """
    # IP头部通常是20字节,ICMP从第21字节开始
    icmp_header = packet[20:28]

    # 解析ICMP头部
    icmp_type, icmp_code, checksum, packet_id, sequence = struct.unpack(
        '!BBHHH', icmp_header
    )

    # 解析数据部分的时间戳
    send_time = struct.unpack('!d', packet[28:36])[0]

    return icmp_type, icmp_code, packet_id, sequence, send_time

def ping(host, count=4, timeout=3):
    """
    ping指定主机

    参数:
        host: 目标主机名或IP地址
        count: ping的次数
        timeout: 超时时间(秒)

    返回:
        统计信息字典
    """
    print(f"PING {host}")
    print()

    # 解析主机名为IP地址
    try:
        dest_ip = socket.gethostbyname(host)
        print(f"目标IP: {dest_ip}")
        print("-" * 60)
    except socket.gaierror:
        print(f"❌ 无法解析主机名: {host}")
        return None

    # 创建原始socket(需要root权限)
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
        sock.settimeout(timeout)
    except PermissionError:
        print("❌ 需要管理员权限运行此程序")
        print("💡 Linux/Mac: sudo python day05_simple_ping.py")
        print("💡 Windows: 以管理员身份运行")
        return None

    # 统计信息
    packet_id = 12345  # 可以用进程ID
    sent = 0
    received = 0
    rtt_list = []

    # 发送ping包
    for seq in range(count):
        try:
            # 创建ICMP包
            packet = create_icmp_packet(packet_id, seq)

            # 发送
            send_time = time.time()
            sock.sendto(packet, (dest_ip, 0))
            sent += 1

            # 接收回复
            try:
                reply_packet, addr = sock.recvfrom(1024)
                recv_time = time.time()

                # 解析回复
                icmp_type, icmp_code, reply_id, reply_seq, packet_send_time = \
                    parse_icmp_reply(reply_packet)

                # 检查是否是我们的包的回复
                if icmp_type == 0 and reply_id == packet_id and reply_seq == seq:
                    # 计算RTT
                    rtt = (recv_time - packet_send_time) * 1000  # 转换为毫秒
                    rtt_list.append(rtt)
                    received += 1

                    # 获取TTL
                    ttl = struct.unpack('!B', reply_packet[8:9])[0]

                    print(f"✅ 来自 {dest_ip}: icmp_seq={seq} ttl={ttl} "
                          f"time={rtt:.2f} ms")
                else:
                    print(f"⚠️  收到意外的ICMP包 type={icmp_type}")

            except socket.timeout:
                print(f"⏱️  icmp_seq={seq} 请求超时")

            # 等待1秒再发送下一个包
            if seq < count - 1:
                time.sleep(1)

        except Exception as e:
            print(f"❌ 发送失败: {e}")

    sock.close()

    # 打印统计信息
    print()
    print("-" * 60)
    print(f"📊 {host} ping统计信息:")
    print(f"   发送: {sent} 个包")
    print(f"   接收: {received} 个包")
    print(f"   丢失: {sent - received} 个包 ({((sent-received)/sent*100):.1f}% loss)")

    if rtt_list:
        print(f"   RTT: 最小={min(rtt_list):.2f}ms, "
              f"平均={sum(rtt_list)/len(rtt_list):.2f}ms, "
              f"最大={max(rtt_list):.2f}ms")
    print("-" * 60)

    return {
        'sent': sent,
        'received': received,
        'rtt_list': rtt_list
    }

def main():
    """
    主函数
    """
    print("=" * 60)
    print("简易Ping工具".center(60))
    print("=" * 60)
    print()

    # 获取目标主机
    if len(sys.argv) > 1:
        host = sys.argv[1]
    else:
        host = input("请输入要ping的主机名或IP: ").strip()
        if not host:
            host = "baidu.com"  # 默认

    # 执行ping
    result = ping(host, count=4, timeout=3)

    if result and result['received'] > 0:
        print()
        print("🎉 Ping完成!")
    else:
        print()
        print("❌ Ping失败或网络不可达")

if __name__ == "__main__":
    main()

运行程序

# Linux/Mac(需要root权限)
sudo python code/day05/day05_simple_ping.py baidu.com

# Windows(以管理员身份运行)
python code\day05\day05_simple_ping.py baidu.com

# 不带参数运行,会提示输入主机名
sudo python code/day05/day05_simple_ping.py

预期输出

============================================================
                       简易Ping工具
============================================================

PING baidu.com

目标IP: 220.181.38.148
------------------------------------------------------------
✅ 来自 220.181.38.148: icmp_seq=0 ttl=52 time=28.50 ms
✅ 来自 220.181.38.148: icmp_seq=1 ttl=52 time=27.82 ms
✅ 来自 220.181.38.148: icmp_seq=2 ttl=52 time=28.15 ms
✅ 来自 220.181.38.148: icmp_seq=3 ttl=52 time=29.20 ms

------------------------------------------------------------
📊 baidu.com ping统计信息:
   发送: 4 个包
   接收: 4 个包
   丢失: 0 个包 (0.0% loss)
   RTT: 最小=27.82ms, 平均=28.42ms, 最大=29.20ms
------------------------------------------------------------

🎉 Ping完成!

🔍 代码详解

1. 为什么需要root权限?

sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)

原始套接字(SOCK_RAW)可以构造任意的IP包,这是一个强大的功能,所以操作系统要求必须有管理员权限才能使用。

2. 校验和的计算

校验和用于检测数据在传输过程中是否损坏:

# 原理:
1. 将数据按16位分组
2. 所有分组相加
3. 如果有进位加到低16位
4. 对结果取反码

# 接收方验证:
1. 对收到的数据包括校验和做同样的计算
2. 如果结果是0xFFFF说明数据完整
3. 否则说明数据损坏

3. TTL的含义

TTL (Time To Live) = 生存时间

初始值:通常是64或128
每经过一个路由器:TTL -= 1
当TTL = 0:路由器丢弃该包,返回超时错误

作用:防止数据包在网络中无限循环

4. 时间戳的作用

# 发送时在数据部分嵌入时间戳
data = struct.pack('!d', time.time())

# 收到回复时,解析时间戳
send_time = struct.unpack('!d', packet[28:36])[0]

# 计算RTT
rtt = (recv_time - send_time) * 1000  # 毫秒

🎓 知识小结

今天学到了什么?

  1. ✅ ping用于测试网络连通性和延迟
  2. ✅ ping基于ICMP协议(类型8请求,类型0回复)
  3. ✅ RTT是数据包往返的总时间
  4. ✅ TTL表示数据包还能经过多少个路由器
  5. ✅ 实现ping需要使用原始套接字

重要概念

概念含义单位
RTT往返时间毫秒(ms)
TTL生存时间跳数(hops)
丢包率丢失的包占总数的比例百分比(%)
ICMP网络控制消息协议-

ping命令速查

# 基本用法
ping 目标主机

# 常用选项
-c 次数      # Linux/Mac: 指定ping次数
-n 次数      # Windows: 指定ping次数
-i 间隔      # 指定发送间隔(秒)
-s 大小      # 指定数据包大小
-t           # Windows: 持续ping

💪 练习题

练习1:实际操作

使用ping命令测试以下主机,记录RTT和TTL:

  1. baidu.com
  2. google.com
  3. 你的路由器(通常是192.168.1.1)
  4. 本机(127.0.0.1)

练习2:理解TTL

如果ping baidu.com的TTL是52,初始TTL是64,问:

  1. 数据包经过了多少个路由器?
  2. 为什么不同的主机TTL不一样?

练习3:分析丢包

如果ping一个网站,出现以下情况,可能的原因是什么?

  1. 100%丢包
  2. 偶尔丢包(10-20%)
  3. RTT很高(>500ms)

练习4:代码扩展

扩展ping工具:

  1. 添加图形化的RTT变化显示
  2. 支持ping多个主机
  3. 将结果保存到文件
  4. 添加声音提示(连通时播放提示音)

练习5:思考题

  1. 为什么ping使用ICMP而不是TCP或UDP?
  2. 能否通过ping判断对方的操作系统?(提示:看TTL初始值)
  3. 防火墙可以阻止ping吗?

📚 扩展阅读

ping的高级用法

测试MTU(最大传输单元)

# Linux/Mac
ping -M do -s 1472 baidu.com

# Windows
ping -f -l 1472 baidu.com

# 如果成功,说明MTU >= 1500
# 如果失败,逐渐减小数值直到成功

洪水ping(压力测试)

# Linux(需要root权限)
sudo ping -f baidu.com

# 说明:尽可能快地发送包,用于压力测试
# ⚠️ 注意:不要对公共服务器使用,可能被视为攻击

不同操作系统的TTL初始值

操作系统TTL初始值
Linux64
Windows128
Cisco路由器255
Unix/BSD255

通过ping返回的TTL,可以大致判断对方的操作系统类型!

ICMP的其他用途

  1. Traceroute:通过逐步增加TTL来追踪路由
  2. MTU发现:确定最佳数据包大小
  3. 网络诊断:检测网络配置问题
  4. 错误报告:通知源主机网络错误

🎯 明天预告

第6天:网络命令进阶 - traceroute

我们将学习:

  • traceroute的工作原理
  • 如何追踪数据包的完整路径
  • 理解TTL在路由追踪中的作用
  • 编写自己的traceroute工具

💡 今日金句:ping通网络,就像听到远方朋友的回音!

👉 上一天:TCP/IP四层模型 | 返回目录 | 下一天:traceroute详解