一 . 实验目的

熟悉基于 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)/2<mintime):
        mintime=(t2-t1)/2
    elif((t2-t1)/2>maxtime):
        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接收。

更多推荐

python-套接字基础与 UDP 通信