一 . 实验目的
熟悉基于 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. 首先在服务器端通过调用s o c k e t ( ) 创建套接字来启动一个服务器;
2. 服务器调用 bind( ) 指定服务器的套接字地址,然后调用 等待接收数据。
3. 在客户端调用 s o c k e t ( ) 创建套接字,然后调用 s e n d t o ( ) 向服务器发送数据。
4. 服务器接收到客户端发来的数据后,调用 s e n d t o ( ) 向客户发送应答数据,
5. 客户调用 r e c v f r o m ( ) 接收服务器发来的应答数据。
6. 一旦数据传输结束,服务器和客户通过调用 close( ) 来关闭套接字。
基于 Python的 UDP 程序工作详细流程如图1所示。
基于 Python 进行 UDP 消息的接收操作时,Python 程序将工作在阻塞状态,即未收到数据包时,Python 程序将挂起等待而不会继续执行。如果程序运行中网络连接出现了问题,导致数据包无法及时到达,这种阻塞式的工作模式将会严重的干扰程序的执行。为了解决这个问题,Python 的套接字通信库提供了一种“超时”机制来防止程序卡死。在 Python 套接字程序中,套接字对象提供了一个Settimeout()方法来限制 r e c v f r o m ( ) 函数的等待时间,当 recvfrom() 函数阻塞等待超过这个时间(一般称为“超时时间”)后仍然没有收到数据时,程序将会抛出一个异常来说明发生了等待数据接收超时事件。在编写 Python 网络通信程序时,可以利用这个机制来判断是否接收数据超时。
四 . 实验条件
• 装有 python 环境的电脑两台;
• 局域网环境;
• 服务器程序;
• Python 语言参考手册 – UDP 部分1。
五 . 实验步骤
使用提供的Python 代码,实现了一个 UDP 服务器,该服务器还会模拟丢失30% 的客户端数据包。参考代码,基于 Python 按照实验任务要求完成 Ping 程序的客户端。
注意:在运行客户端程序前,需要先运行服务器端代码。
编写成功后,使用客服端 Ping 程序经 UDP 向目标服务器发送 10 个 ping 报文。要求:
1. 使用 UDP 发送 ping 消息(注意:因为 UDP 是无连接协议,不需要建立连接。);
2. 如果服务器在 1 秒内响应,则打印该响应消息;计算并打印每个数据包的往返时间 RTT(以秒为单位);
3. 否则,打印“请求超时”(中英文皆可)。
在开发过程中,可以将客户端程序和服务器程序放在同一台电脑上进行测试。在完成代码调试后,可以尝试将客户端和服务器代码运行在不同网络环境,记录并分析结果。
六 . 进阶任务
尝试修改代码,在程序运行结束时,计算所有 ping 消息的最小、最大和平均 RTT,并计算丢包率(丢失数据包在总数据包中所占有的百分比)。即构造出一个符合标准 Windows 版 Ping 程序工作模式的基于 UDP 版 Ping 程序。
七 . 代码及说明
代码说明在代码注释中给出,具体流程如 三、实验原理
服务器 server.py代码
from socket import *
import random
#服务端通过调用socket创建套接字来启动服务器
serverSocket=socket(AF_INET,SOCK_DGRAM)
#服务器调用 bind( ) 指定服务器的套接字地址
serverSocket.bind(('',7777))
while True:
rand=random.randint(0,10)
#服务端调用recvfrom()等待接收数据,此时阻塞
message,address=serverSocket.recvfrom(1024)
#成功接收消息后继续运行
print(message)
message=message.upper()
#模拟丢失30%的客户端数据包
if rand<4:
continue
#服务器接收到客户端发来的数据后,调用sendto()向客户发送应答数据
serverSocket.sendto(message,address)
客户端 client.py代码
# -*- coding: UTF-8 -*-
from socket import*
import time
#客户端调用socket()创建套接字
clientSocket=socket(AF_INET,SOCK_DGRAM)
#使用settimeout函数限制recvfrom()函数的等待时间为1秒
clientSocket.settimeout(1)
count=0#计数接收pong报文失败次数
start=0#第一次成功接收pong报文后用于计算RRT
totaltime=0#用于计算平均RRT
#发送10次ping报文
for i in range(10):
#报文内容
print("test_"+str(i))
t1=time.time()#RRT开始计数时间
#将信息转换为byte后发送到指定服务器端
clientSocket.sendto("ping".encode("utf-8"),("172.25.9.97",7777))
try:
#调用recvfrom()函数接收服务器发来的应答数据
message,address=clientSocket.recvfrom(1024)
#超时处理,等到时间超过1秒,捕获抛出的异常后打印丢失报文,进行下一步操作
except:
print("out of time!!!")
count=count+1
continue
t2=time.time()#RRT结束计数时间
#计算ping消息的最小、最大和平均RRT,并计算丢包率
if(start==0):
mintime=(t2-t1)/2
maxtime=(t2-t1)/2
start=1
elif((t2-t1)/2maxtime):
maxtime=(t2-t1)/2
totaltime=totaltime+(t2-t1)/2
print('%.15f'%((t2-t1)/2))
print('min_RRT:'+str(mintime))
print('max_RRT:'+str(maxtime))
print('average_RRT:'+str(totaltime/(10-count)))
print('packet loss rate:'+str(count/10))
八 . 运行结果
实验室电脑server - 个人电脑client
在实验室电脑中打开cmd并输入ipconfig查询得到ip地址。在个人电脑的client.py中将sendto()函数中要发送到的服务器地址与端口号设置成实验室电脑的ip地址与设置好的端口号("172.25.9.97",7777)。先在实验室电脑上运行server.py,再在个人电脑上运行client.py。运行两次的client结果
个人电脑server - 个人电脑client
在个人电脑的client.py中将sendto()函数中要发送到的服务器地址设置成127.0.0.1。
先打开一个cmd运行server.py,再运行client.py。运行结果
结果分析
实验室电脑server - 个人电脑client和个人电脑server - 个人电脑client的最小RRT显示为0.0,在这里显示的是小数点后15位,可以看作没有延时的情况。
上述可见RRT时间在实验室电脑server - 个人电脑client与个人电脑server - 个人电脑client表现存在差异,实验室电脑server - 个人电脑client之间的RRT明显较大,这与我们的预期一致。两种方式都出现了无阻塞(RRT为0.0)和阻塞的情况。
丢包率在此使用随机数,所以并没有对比的实际意义。
遇到问题
由于python 中socket在使用settimeout后因超时抛出的异常不能被IOError接住,所以直接使用except接收。