第5天:网络命令入门 - ping
“ping一下,网络通不通立刻知道。”
📚 今日目标
- 理解ping命令的工作原理
- 掌握ICMP协议基础知识
- 学会使用ping命令进行网络诊断
- 编写自己的ping工具
🎯 什么是ping?
生活中的例子
ping = 喊话游戏
你:喂!能听到吗? (发送)
朋友:能听到! (回应)
你:太好了,通信正常! (确认)
ping命令就是用来测试网络连通性的工具,它会发送一个数据包到目标主机,然后等待回应。
ping的作用
- 测试连通性:目标主机是否可达
- 测量延迟:数据往返需要多少时间
- 检测丢包:有多少数据包丢失了
- 诊断网络问题:定位网络故障
📡 ICMP协议
什么是ICMP?
ICMP(Internet Control Message Protocol,互联网控制消息协议)是TCP/IP协议族的一部分,工作在网际层(IP层)。
ICMP的作用
ICMP = 网络的"通讯员"
主要任务:
1. 报告错误("对方不在家")
2. 诊断问题("ping测试")
3. 提供反馈("超时了")
常见的ICMP消息类型
| 类型 | 名称 | 用途 |
|---|---|---|
| 0 | Echo Reply | ping的回应 |
| 3 | Destination Unreachable | 目标不可达 |
| 5 | Redirect | 重定向 |
| 8 | Echo Request | ping的请求 |
| 11 | Time 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 # 毫秒
🎓 知识小结
今天学到了什么?
- ✅ ping用于测试网络连通性和延迟
- ✅ ping基于ICMP协议(类型8请求,类型0回复)
- ✅ RTT是数据包往返的总时间
- ✅ TTL表示数据包还能经过多少个路由器
- ✅ 实现ping需要使用原始套接字
重要概念
| 概念 | 含义 | 单位 |
|---|---|---|
| RTT | 往返时间 | 毫秒(ms) |
| TTL | 生存时间 | 跳数(hops) |
| 丢包率 | 丢失的包占总数的比例 | 百分比(%) |
| ICMP | 网络控制消息协议 | - |
ping命令速查
# 基本用法
ping 目标主机
# 常用选项
-c 次数 # Linux/Mac: 指定ping次数
-n 次数 # Windows: 指定ping次数
-i 间隔 # 指定发送间隔(秒)
-s 大小 # 指定数据包大小
-t # Windows: 持续ping
💪 练习题
练习1:实际操作
使用ping命令测试以下主机,记录RTT和TTL:
- baidu.com
- google.com
- 你的路由器(通常是192.168.1.1)
- 本机(127.0.0.1)
练习2:理解TTL
如果ping baidu.com的TTL是52,初始TTL是64,问:
- 数据包经过了多少个路由器?
- 为什么不同的主机TTL不一样?
练习3:分析丢包
如果ping一个网站,出现以下情况,可能的原因是什么?
- 100%丢包
- 偶尔丢包(10-20%)
- RTT很高(>500ms)
练习4:代码扩展
扩展ping工具:
- 添加图形化的RTT变化显示
- 支持ping多个主机
- 将结果保存到文件
- 添加声音提示(连通时播放提示音)
练习5:思考题
- 为什么ping使用ICMP而不是TCP或UDP?
- 能否通过ping判断对方的操作系统?(提示:看TTL初始值)
- 防火墙可以阻止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初始值 |
|---|---|
| Linux | 64 |
| Windows | 128 |
| Cisco路由器 | 255 |
| Unix/BSD | 255 |
通过ping返回的TTL,可以大致判断对方的操作系统类型!
ICMP的其他用途
- Traceroute:通过逐步增加TTL来追踪路由
- MTU发现:确定最佳数据包大小
- 网络诊断:检测网络配置问题
- 错误报告:通知源主机网络错误
🎯 明天预告
第6天:网络命令进阶 - traceroute
我们将学习:
- traceroute的工作原理
- 如何追踪数据包的完整路径
- 理解TTL在路由追踪中的作用
- 编写自己的traceroute工具
💡 今日金句:ping通网络,就像听到远方朋友的回音!