套接字编程作业 2:UDP Ping 程序
问题描述:
在这个实验中,你将通过Python使用UDP套接字编程,编写一个ping程序。Ping程序是用来测试网络延迟的工具,客户端通过发送请求到服务器,然后计算往返时间(RTT),并处理可能的丢包。作业的主要任务是:
- 编写一个UDP客户端,向服务器发送10个ping请求。
- 服务器会根据收到的请求返回响应,但30%的请求会被随机丢弃以模拟丢包。
- 客户端应处理超时情况,超时阈值为1秒。
- 每次ping请求后,客户端应输出收到的响应或超时消息,并显示往返时间(RTT)。
你需要实现的客户端程序功能类似于标准的ping
命令,但使用的是UDP协议,而不是ICMP协议。
问题解决流程:
-
理解UDP协议的特点:
- UDP是一种无连接、轻量级但不可靠的传输协议,数据包可能会丢失,不保证数据的到达顺序。
- 在Ping程序中,我们使用UDP,因为其简单且能模拟不可靠的网络通信。
-
服务器端实现:
- 我们提供了一个服务器端程序
UDPPingerServer.py
,它接收来自客户端的请求,随机丢弃30%的请求,剩下的请求则返回给客户端。 - 你需要在本地或远程服务器上运行该服务器程序,然后实现客户端程序与其通信。
- 我们提供了一个服务器端程序
-
客户端功能要求:
- 使用UDP向服务器发送10次ping请求,每次请求中包含当前的发送时间。
- 服务器返回响应后,计算该请求的往返时间(RTT)。
- 如果请求超过1秒未收到响应,则显示"Request timed out"。
- 最终输出每次ping的RTT或者超时消息。
步骤 1:服务器代码
服务器代码已经提供,先确保它在本地或远程服务器上运行。
python
# UDPPingerServer.py
from socket import *
import random
# 创建一个UDP套接字
serverSocket = socket(AF_INET, SOCK_DGRAM)
# 绑定服务器的IP地址和端口号
serverSocket.bind(('', 12000))
while True:
# 生成一个随机数,用于模拟丢包
rand = random.randint(0, 10)
# 接收客户端消息和地址
message, address = serverSocket.recvfrom(1024)
# 将收到的消息转换为大写
message = message.upper()
# 如果随机数小于4,模拟丢包,不返回任何消息
if rand < 4:
continue
# 否则,将消息返回给客户端
serverSocket.sendto(message, address)
- 该服务器程序使用UDP协议监听端口
12000
,并随机丢弃30%的客户端请求。
步骤 2:客户端代码
我们现在需要编写客户端程序,它将向服务器发送10个ping请求,并显示每次请求的结果,包括往返时间或超时。
python
import time
from socket import *
# 创建UDP套接字
clientSocket = socket(AF_INET, SOCK_DGRAM)
# 设置超时时间为1秒
clientSocket.settimeout(1)
# 服务器的地址和端口
serverName = 'localhost' # 本地运行时使用 'localhost'
serverPort = 12000
# 发送10次ping请求
for sequence_number in range(1, 11):
# 获取当前时间戳
sendTime = time.time()
# 构造ping消息
message = f'Ping {sequence_number} {sendTime}'
try:
# 向服务器发送消息
clientSocket.sendto(message.encode(), (serverName, serverPort))
# 等待服务器响应,超时1秒
response, serverAddress = clientSocket.recvfrom(1024)
# 计算RTT
rtt = time.time() - sendTime
# 打印服务器返回的消息和RTT
print(f'Received from server: {response.decode()} RTT = {rtt:.4f} seconds')
except timeout:
# 超时处理
print('Request timed out')
# 关闭套接字
clientSocket.close()
代码解析:
- 创建UDP套接字 :
clientSocket = socket(AF_INET, SOCK_DGRAM)
使用UDP协议,适合无连接的通信。 - 设置超时 :
clientSocket.settimeout(1)
设置接收数据包的超时时间为1秒,如果在1秒内未收到响应,则抛出timeout
异常。 - 发送ping消息 :
clientSocket.sendto(message.encode(), (serverName, serverPort))
向服务器发送包含序列号和时间戳的ping消息。 - 接收服务器响应 :
clientSocket.recvfrom(1024)
接收服务器返回的消息,并计算消息的往返时间(RTT)。 - 超时处理:如果未收到响应,客户端会输出"Request timed out"。
步骤 3:运行程序
1. 运行服务器:
在本地或远程服务器上运行提供的服务器代码:
bash
python UDPPingerServer.py
2. 运行客户端:
在另一个终端窗口中运行客户端代码:
bash
python UDPPingerClient.py
你会看到客户端向服务器发送10次ping请求的输出,类似于以下结果:
Received from server: PING 1 1663261625.64255 RTT = 0.0021 seconds
Received from server: PING 2 1663261626.64305 RTT = 0.0020 seconds
Request timed out
Received from server: PING 4 1663261628.64400 RTT = 0.0021 seconds
...
- 如果服务器丢弃了请求,客户端会显示
Request timed out
。 - 如果服务器返回了响应,客户端会显示往返时间(RTT)。
可选练习1
客户端程序需要在发送10次Ping请求后计算最小、最大和平均RTT,同时计算丢包率。我们可以通过保存每次Ping的RTT值,最后计算这些统计数据。具体步骤如下:
实现步骤
- 存储每次Ping的RTT:为RTT创建一个列表,每次接收到响应时,将RTT添加到该列表中。
- 统计丢包情况:为超时次数创建一个计数器,每次超时时递增。
- 计算最小、最大和平均RTT:在发送完所有Ping请求后,通过列表计算最小值、最大值和平均值。
- 计算丢包率:丢包率 = (丢失的包数 / 总包数)× 100%。
修改后的代码
python
import time
from socket import *
# 创建UDP套接字
clientSocket = socket(AF_INET, SOCK_DGRAM)
# 设置超时时间为1秒
clientSocket.settimeout(1)
# 服务器的地址和端口
serverName = 'localhost' # 本地运行时使用 'localhost'
serverPort = 12000
# 初始化变量
rtt_list = [] # 存储每次成功Ping的RTT
timeout_count = 0 # 记录超时的次数
total_pings = 10 # 发送的Ping总次数
# 发送10次ping请求
for sequence_number in range(1, total_pings + 1):
# 获取当前时间戳
sendTime = time.time()
# 构造ping消息
message = f'Ping {sequence_number} {sendTime}'
try:
# 向服务器发送消息
clientSocket.sendto(message.encode(), (serverName, serverPort))
# 等待服务器响应,超时1秒
response, serverAddress = clientSocket.recvfrom(1024)
# 计算RTT
rtt = time.time() - sendTime
rtt_list.append(rtt) # 将RTT加入列表
# 打印服务器返回的消息和RTT
print(f'Received from server: {response.decode()} RTT = {rtt:.4f} seconds')
except timeout:
# 超时处理,计数器增加
print('Request timed out')
timeout_count += 1
# 关闭套接字
clientSocket.close()
# 如果有成功的Ping,则计算最小、最大和平均RTT
if rtt_list:
min_rtt = min(rtt_list)
max_rtt = max(rtt_list)
avg_rtt = sum(rtt_list) / len(rtt_list)
print(f'\n--- Ping statistics ---')
print(f'{total_pings} packets transmitted, {len(rtt_list)} packets received, '
f'{timeout_count / total_pings * 100:.1f}% packet loss')
print(f'rtt min/avg/max = {min_rtt:.4f}/{avg_rtt:.4f}/{max_rtt:.4f} seconds')
else:
print(f'\n--- Ping statistics ---')
print(f'{total_pings} packets transmitted, 0 packets received, 100.0% packet loss')
代码解析
rtt_list
:这是一个列表,用于存储每次成功的RTT值。当接收到响应时,将RTT值添加到该列表中。timeout_count
:这是一个计数器,用于记录超时(丢包)的次数。每当请求超时时,计数器会加1。- RTT计算 :程序结束后,检查
rtt_list
是否为空。如果有接收到的Ping响应,则计算最小、最大和平均RTT,并打印出来。 - 丢包率计算 :丢包率通过
timeout_count / total_pings * 100
计算,表示为百分比形式。
输出示例
假设服务器响应如下:
Received from server: PING 1 1663261625.64255 RTT = 0.0021 seconds
Request timed out
Received from server: PING 3 1663261626.64305 RTT = 0.0020 seconds
Request timed out
Received from server: PING 5 1663261627.64400 RTT = 0.0021 seconds
...
--- Ping statistics ---
10 packets transmitted, 6 packets received, 40.0% packet loss
rtt min/avg/max = 0.0020/0.0021/0.0022 seconds
通过这些修改,客户端程序在完成所有Ping操作后,会输出最小、最大和平均RTT,并计算出丢包率。这种行为模拟了标准ping
命令的功能,提供了更加详细的网络通信统计信息。