Q: 使用Python实现Ping,可传入参数timeout,count。要求能将每个Ping包能否Ping的结果及发送时间记录在文件中最后的统计结果需给出丢包率、最小Ping通时间、最大Ping通时间、平均Ping通时间。要求:至少使用2种不同的实现方式.
分析:实现ping, 翻译一下就是实现网络层协议
第一种方法是创建一个进程(这里的话socket是网络套接字,最好看一下《网络是怎样连接的》,复习一下套接字的基础内容,结合python代码以及Python中socket库的使用,是较为合适的方法):创建进程之后,调用系统的ping命令,这应该不是合格的做法。
import subprocess
import re
import statistics
def ping_with_subprocess(host, timeout=1, count=4):
ping_result = subprocess.run(['ping', '-c', str(count), '-W', str(timeout), host], capture_output=True, text=True)
output = ping_result.stdout
# 解析 ping 结果
packets = re.findall(r'(\d+) packets transmitted, (\d+) received', output)
if packets:
transmitted = int(packets[0][0])
received = int(packets[0][1])
lost = transmitted - received
loss_rate = (lost / transmitted) * 100
rtt_times = re.findall(r'time=(\d+\.\d+) ms', output)
rtt_times = [float(time) for time in rtt_times]
min_rtt = min(rtt_times) if rtt_times else None
max_rtt = max(rtt_times) if rtt_times else None
avg_rtt = statistics.mean(rtt_times) if rtt_times else None
# 将结果写入文件
with open('ping_result.txt', 'a') as file:
file.write(f'Host: {host}\n')
file.write(f'Transmitted: {transmitted}\n')
file.write(f'Received: {received}\n')
file.write(f'Lost: {lost}\n')
file.write(f'Loss rate: {loss_rate}%\n')
file.write(f'Min RTT: {min_rtt} ms\n')
file.write(f'Max RTT: {max_rtt} ms\n')
file.write(f'Average RTT: {avg_rtt} ms\n')
file.write('---\n')
return loss_rate, min_rtt, max_rtt, avg_rtt
else:
return None, None, None, None
# 示例用法
host = 'www.google.com'
timeout = 1
count = 4
ping_with_subprocess(host, timeout, count)
第二种方法是:使用socket模块发送ICMP Echo请求【为什么不能实现Ping的原理呢?】
import socket
import struct
import time
import statistics
def ping_with_socket(host, timeout=1, count=4):
icmp = socket.getprotobyname('icmp')
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
sock.settimeout(timeout)
seq_number = 0
transmitted = 0
received = 0
rtt_times = []
# 发送 ICMP Echo 请求
for i in range(count):
seq_number += 1
icmp_header = struct.pack('!BBHHH', 8, 0, 0, seq_number, 1)
checksum = calculate_checksum(icmp_header)
icmp_header = struct.pack('!BBHHH', 8, 0, checksum, seq_number, 1)
start_time = time.time()
try:
sock.sendto(icmp_header, (host, 0))
transmitted += 1
# 接收 ICMP Echo 回复
recv_packet, addr = sock.recvfrom(1024)
end_time = time.time()
rtt = (end_time - start_time) * 1000
rtt_times.append(rtt)
received += 1
# 将结果写入文件
with open('ping_result.txt', 'a') as file:
file.write(f'Host: {host}\n')
file.write(f'Sequence number: {seq_number}\n')
file.write(f'RTT: {rtt} ms\n')
file.write('---\n')
except socket.timeout:
# 超时未收到回复
with open('ping_result.txt', 'a') as file:
file.write(f'Host: {host}\n')
file.write(f'Sequence number: {seq_number}\n')
file.write('Timeout\n')
file.write('---\n')
sock.close()
lost = transmitted - received
loss_rate = (lost / transmitted) * 100
min_rtt = min(rtt_times) if rtt_times else None
max_rtt = max(rtt_times) if rtt_times else None
avg_rtt = statistics.mean(rtt_times) if rtt_times else None
# 将统计结果写入文件
with open('ping_result.txt',
第三种方法是使用ping3,但是还是调包,与第一种方法是一样的。
如果是通过ping的原理,那就与网络套接字相关了:
import os
import sys
import struct
import socket
import select
import time
ICMP_ECHO_REQUEST = 8 # ICMP Echo Request类型码
def checksum(source_string):
"""
计算校验和
"""
sum = 0
countTo = (len(source_string) / 2) * 2
count = 0
while count < countTo:
thisVal = ord(source_string[count + 1]) * 256 + ord(source_string[count])
sum = sum + thisVal
sum = sum & 0xffffffff # 处理溢出
count = count + 2
if countTo < len(source_string):
sum = sum + ord(source_string[len(source_string) - 1])
sum = sum & 0xffffffff # 处理溢出
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
answer = ~sum
answer = answer & 0xffff
# 根据主机字节序转换为网络字节序
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
def receive_ping(my_socket, ID, timeout):
"""
接收Ping回复报文
"""
timeLeft = timeout
while True:
startedSelect = time.time()
whatReady = select.select([my_socket], [], [], timeLeft)
howLongInSelect = (time.time() - startedSelect)
if whatReady[0] == []: # 超时
return
timeReceived = time.time()
recPacket, addr = my_socket.recvfrom(1024)
# 解析ICMP报文
icmpHeader = recPacket[20:28]
type, code, checksum, packetID, sequence = struct.unpack("bbHHh", icmpHeader)
if packetID == ID:
bytesInDouble = struct.calcsize("d")
timeSent = struct.unpack("d", recPacket[28:28 + bytesInDouble])[0]
return timeReceived - timeSent
timeLeft = timeLeft - howLongInSelect
if timeLeft <= 0:
return
def send_ping(my_socket, dest_addr, ID):
"""
发送Ping请求报文
"""
dest_addr = socket.gethostbyname(dest_addr)
# 构建ICMP报文
my_checksum = 0
header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1)
bytesInDouble = struct.calcsize("d")
data = (192 - bytesInDouble) * "Q"
data = struct.pack("d", time.time()) + data
# 计算校验和
my_checksum = checksum(header + data)
# 重新构建ICMP报文,加入校验和
header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1)
packet = header + data
# 发送报文
my_socket.sendto(packet, (dest_addr, 1))
def ping(dest_addr, timeout=1, count=4):
"""
Ping函数
"""
try:
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
except socket.error as e:
print("Socket创建失败: %s" % e)
sys.exit()
ID = os.getpid() & 0xFFFF # 用当前进程ID作为标识符
for i in range(count):
send_ping(my_socket, dest_addr, ID)
delay = receive_ping(my_socket, ID, timeout)
if delay is None:
print("Ping %s 超时" % dest_addr)
else:
print("Ping %s 回复,延迟=%.3fms" % (dest_addr, delay * 1000))
my_socket.close()
# 示例用法
ping("www.google.com")
使用socket创建一个原始套接字,构建ICMP报文并发送给目标主机,并且可以接收到回复报文。