套接字编程作业2:UDPping程序
作业描述
《计算机网络:自顶向下方法》中第二章末尾给出了此编程作业的简单描述:
在这个编程作业中,你将用Python编写一个客户ping程序。该客户将发送一个简单的ping报文,接受一个从服务器返回的pong报文,并确定从该客户发送ping报文到接收到pong报文为止的时延。该时延称为往返时延(RTT)。由该客户和服务器提供的功能类似于在现代操作系统中可用的标准ping程序,然而,标准的ping使用互联网控制报文协议(ICMP)(我们将在第4章中学习ICMP)。此时我们将创建一个非标准(但简单)的基于UDP的ping程序。
你的ping程序经UDP向目标服务器发送10个ping报文,对于每个报文,当对应的pong报文返回时,你的客户要确定和打印RTT。因为UDP是一个不可靠协议,由客户发送的分组可能会丢失。为此,客户不能无限期地等待对ping报文的回答。客户等待服务器回答的时间至多为1秒;如果没有收到回答,客户假定该分组丢失并相应地打印一条报文。
在此作业中,我们给出服务器的完整代码(在配套网站中可以找到。你的任务是编写客户代码,该代码与服务器代码非常类似。建议你先仔细学习服务器的代码,然后编写你的客户代码,可以不受限制地从服务器代码中剪贴代码行。
可选练习:
- 目前,程序计算每个数据包的往返时间(RTT),并单独打印出来。请按照标准ping程序的模式修改。您需要在客户端每次ping后显示最小,最大和平均RTT。另外,还需计算丢包率(百分比)。
- UDP Ping的另一个类似的应用是UDP Heartbeat。心跳可用于检查应用程序是否已启动并运行,并报告单向丢包。客户端在UDP数据包中将一个序列号和当前时间戳发送给正在监听客户端心跳的服务器。服务器收到数据包后,计算时差,报告丢包(若发生)。如果心跳数据包在指定的一段时间内丢失,我们可以假设客户端应用程序已经停止。实现UDP Heartbeat(客户端和服务器端)。您需要修改给定的UDPPingerServer.py和您自己的UDP ping客户端。
需求实现
首先建立一个UDP套接字,并指定目的IP地址和端口。
然后使用一个循环来发送数据包,共循环10次。由于需要对往返时间进行计算,所以需要sendto()前和recvfrom()后提取时间。两次相减,可得到每个消息的往返时延(RTT)。对于其他的信息,设置相关变量进行记录即可,大家可以使用其他的方法进行数据的采集最后分析显示结果。
编写代码及解释
(1)客户端请求
# UDPPingClient.py
from socket import *
import time
serverName = '113.54.200.50' # 服务器地址
serverPort = 12000 # 服务器指定的端口
clientSocket = socket(AF_INET, SOCK_DGRAM) # 创建UDP套接字
clientSocket.settimeout(1) # 设置套接字超时值为1秒
timeList = [] # 创建空列表,存放TTL
totalReq = 10 # 设置发送报文数
loseReq = 0 # 丢包数,为了后面计算丢包率
totalTime = 0 # 总时间,方便后面计算平均往返时间
for i in range(0, totalReq):
sendTime = time.time()
message = ('Ping %d %s' % (i + 1, sendTime)).encode() # 生成数据报,编码为bytes以便发送
try:
clientSocket.sendto(message, (serverName, serverPort)) # 将信息发送到服务器
modifiedMessage, serverAddress = clientSocket.recvfrom(1024) # 从服务器接收信息,同时也能得到服务器地址
TTL1 = time.time() - sendTime # 计算往返时间
timeList.append(TTL1) # 将TTL放入列表,方便后面进行数据统计
totalTime += TTL1
print('Request sequence %d: Reply from %s RTT = %.5fs' % (i + 1, serverName, TTL1)) # 显示信息
except Exception as e: # 接收多种异常,给每一个进入到此 except 块的异常,起一个统一的别名 e
TTL2 = time.time() - sendTime # 计算往返时间
timeList.append(TTL2) # 将TTL放入列表,方便后面进行数据统计
loseReq += 1
totalTime += TTL2
print('Request sequence %d: Request timed out' % (i + 1))
# ping后显示最小,最大和平均RTT。另外,还需计算丢包率(百分比)
print('\n minRTT: %0.5fs\t maxRTT: %0.5fs\t avgRTT: %0.5fs'
% (min(timeList), max(timeList), totalTime/len(timeList)))
print('\n packetLoss percent: {:.2%}'.format(loseReq/totalReq))
clientSocket.close() # 关闭套接字
(2)服务端设置
# UDPPingerServer.py
# 生成随机丢失的数据包,导入随机数包
from socket import *
import random
# 创建UDP套接字,第一参数表示使用IPV4,第二参数指示套接字为UDP类型
serverSocket = socket(AF_INET, SOCK_DGRAM)
# 申请端口号,并将IP地址一起绑定到套接字;目的地址包含目的端IP和端口号
serverSocket.bind(('', 12000))
# 服务器会一直等待分组到达
while True:
# 产生一个(0,10)的随机整数
rand = random.randint(0, 10)
# Receive the client packet along with the address it is coming from
# 接收客户端信息,从客户端接收UDP数据报
# UDP是无连接的。sendto(),是把UDP数据报发给指定地址;recvfrom()是从指定地址接收UDP数据报。
message, address = serverSocket.recvfrom(1024)
# 将收到的数据转为大写
message = message.upper()
# 如果rand小于4,则我们认为数据包丢失并且不响应
if rand < 4:
continue
# 把更改后的UDP数据报发给指定地址,地址为上面的客户端的地址
serverSocket.sendto(message, address)
运行结果
服务器端:
在一台主机上运行UDPPingerServer.py
,作为接收ping程序数据的服务器。
客户端:
在另一台主机上运行UDPPingClient.py
,实验结果如下:
(1)通过ping程序经UDP向目标服务器发送10个ping报文,对于每个报文,当对应的pong报文返回时,你的客户要确定和打印RTT。因为UDP是一个不可靠协议,由客户发送的分组可能会丢失。为此,客户不能无限期地等待对ping报文的回答。客户等待服务器回答的时间至多为1秒;如果没有收到回答,客户假定该分组丢失并相应地打印一条丢失报文。
(2)目前,程序计算每个数据包的往返时间(RTT),并单独打印出来。按照标准ping程序的模式修改。在客户端每次ping后显示最小,最大和平均RTT。另外,还需计算丢包率(百分比)。