一、实验目的
熟悉基于 Python 进行 UDP 套接字编程的基础知识,掌握使用 UDP 套接字发送和接收数据包, 以及设置正确的套接字超时,了解Ping 应用程序的基本概念,并理解其在简单判断网络状态,例如计算数据包丢失率等统计数据方面的意义。
二、实验内容
1. 操作系统附带的标准ping命令使用ICMP进行通信,本实验要求学生编程实现一个简单的,非标准的,基于UDP 进行通信的ping程序。学生需要用 Python 编写一个Ping客户端。客户 端程序发送一个ping报文,然后接收一个从已经提供的服务器上返回的对应 pong报文,并计算出从该客户发送ping报文到接收到pong 报文为止的往返时延(Round-Trip Time,RTT)。
2. 在客户端程序一次执行过程中,学生编写的的ping 客户端程序需经UDP 向服务器发送10个ping报文。对于每个报文,当对应的 pong 报文返回时,客户端程序要确认并打印输出 RTT 值;在整个执行过程中,客户端程序需要考虑分组丢失情况,客户端最多等待1秒,超过该时长则打印丢失报文。
三、实验方法
UDP 作为一种传输层协议,只提供了无连接通信,且不对传送的数据包进行可靠性保证,因此 只适合于一次传输少量数据的应用场景,如果在传输过程中需要保证可靠性,则这种可靠性应该由 应用层负责。本实验创建的 Ping 程序正是一种不需要保证可靠性的程序,并需要利用这种不可靠性来测量网络的联通情况。
虽然UDP不保证通信的可靠性,包到达的顺序,也不提供流量控制。但正是因为UDP 的控制选 项较少,所以在数据传输过程中延迟小、数据传输效率高, 一些对可靠性要求不高,但对性能等开销更敏感的应用层协议会选择基于UDP进行实现,常见的使用UDP的应用层协议包括 TFTP、SNMP、NFS、DNS、BOOTP等,通常占用53 (DNS) 、69(TFTP) 、161(SNMP)等端口。基于UDP 的无连接客户/服务器在 Python 实现中的工作流程如下:
1. 首先在服务器端通过调用 socket() 创建套接字来启动一个服务器;
2. 服务器调用 bind()指定服务器的套接字地址,然后调用 recvfrom()等待接收数据。
3. 在客户端调用 socket()创建套接字,然后调用 sendto() 向服务器发送数据。
4. 服务器接收到客户端发来的数据后,调用 sendto() 向客户发送应答数据,
5. 客户调用 recvfrom()接收服务器发来的应答数据。
6. 一旦数据传输结束,服务器和客户通过调用 close() 来关闭套接字。
注意在不同的计算机语言实现中,上述调用的名字和具体工作流程可能略有不同。基于Python的 UDP 程序工作详细流程如下图所示。
基于Python进行UDP消息的接收操作时,Python程序将工作在阻塞状态,即未收到数据包时,Python程序将挂起等待而不会继续执行。如果程序运行中网络连接出现了问题,导致数据包无法及时到达,这种阻塞式的工作模式将会严重的干扰程序的执行。为了解决这个问题,Python的套接字通信库提供了一种“超时”机制来防止程序卡死。在 Python套接字程序中,套接字对象提供了一个 settimeout() 方法来限制 recvfrom() 函数的等待时间,当 recvfrom() 函数阻塞等待超过这个时 间(一般称为“超时时间”)后仍然没有收到数据时,程序将会抛出一个异常来说明发生了等待数据接收超时事件。在编写Python网络通信程序时,可以利用这个机制来判断是否接收数据超时。
四、实验步骤
1.根据附件中的程序示例写出客户端和服务端程序:
2.在程序运行结束时,计算所有 ping消息的最小、最大和平均 RTT, 并计算丢包 率(丢失数据包在总数据包中所占有的百分比):
先设置五个值:max_rtt用于记录最大rtt值,min_rtt用于记录最小rtt值,sum_rtt用于记录rtt值的和(丢包的不算),count用于记录成功传输的包数,timeout1用于记录丢包数。
再设置每一个的计算方法:
最后打印输出计算好的值:
五、实验结果
1.服务端输出信息:
2.客户端信息:
六、实验结论
1.服务端:
from socket import *
import random
import time
# 创建一个UDP套接字
serverSocket = socket(AF_INET, SOCK_DGRAM)
# 将套接字绑定到特定的IP地址和端口
serverSocket.bind(('127.0.0.1', 12000))
while True:
rand = random.randint(0, 10)
# 接收消息及客户端地址
message, address = serverSocket.recvfrom(1024)
print("receive", message, "from", address)
# 处理消息,转为大写
message_upper = message.upper()
# 根据随机条件模拟响应
if rand > 4:
rand2 = random.randint(0, 10) # 生成随机延迟时间
time.sleep(rand2 * 0.1) # 模拟处理时间
# 将处理后的消息发送回客户端
serverSocket.sendto(message_upper, address)
print("Send response", message_upper, "to", address)
其中bind函数将套接字绑定至某个主机的接口,recv函数返回服务器发送的信息长度。
2.客户端:
from multiprocessing.connection import Client
from socket import *
import random
import time
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.settimeout(1)
address = ('127.0.0.1', 12000)
def client(message1, address):
start = time.time()
serverSocket.sendto(message1, address)
try:
message2, address = serverSocket.recvfrom(1024)
end = time.time()
rtt = end - start
print(message2, address, rtt)
return rtt
except timeout:
print("time out")
return -1
max_rtt = 0
min_rtt = 1
sum_rtt = 0
count = 0
timeout1 = 0
for x in range(0, 10):
message = x
x1 = str(message)
message1 = x1.encode()
rtt = client(message1, address)
if rtt != -1:
sum_rtt = sum_rtt + rtt
count = count + 1
if rtt > max_rtt:
max_rtt = rtt
if rtt < min_rtt:
min_rtt = rtt
else:
timeout1 = timeout1 + 1
print("max Round-Trip Time is " + str(max_rtt) + " Second")
print("min Round-Trip Time is " + str(min_rtt) + " Second")
print("average Round-Trip Time is " + str(sum_rtt / count) + " Second")
print("package loss rate is " + str(timeout1 * 10) + "%")
其中client函数用于获取rtt值,若包丢失则值为-1。address的值为服务端固定的主机号和端口号。
七、实验小结
本次实验的主要内容为UDP的套接字通信。通过本次实验我熟悉了基于Python进行 UDP套接字编程的基础知识,并且掌握使用UDP套接字发送和接收数据包,以及设置正确的套接字超时。通过模仿ping命令,我了解了ping 应用程序的基本概念,并理解了其在简单判断网络状态,例如计算数据包丢失率等统计数据方面的意义。我在本次实验中遇到了两个问题,第一个是不知道如何将服务端与客户端相连,后来发现只需要将客户端的address设置为服务端绑定的主机号和端口号即可;第二个问题是在计算平均rtt值时,一开始我忽略了丢包的rtt被置为-1的情况,导致第一次计算出来的平均rtt值为负值。看到值为负时我才反应过来,于是将累加语句放到判断条件里,又加了一个计数器count来记录未丢包数,最后输出sum_rtt的值除以count的值,这才解决了问题。