%# -*- coding: utf-8-unix -*-

\section{套接字基础与UDP通信}
\label{sec:c:socket:s:udp}

\subsection{实验目的}
\label{subsec:c:socket:s:udp_object}

熟悉基于Python进行UDP套接字编程的基础知识，掌握使用UDP套接字发送和接收数据包，
以及设置正确的套接字超时，
了解Ping应用程序的基本概念，并理解其在简单判断网络状态，
例如计算数据包丢失率等统计数据方面的意义。

\subsection{实验内容}
\label{subsec:c:socket:s:udp_content}

\begin{enumerate}
  \item 操作系统附带的标准Ping命令使用ICMP进行通信，
        本实验要求学生编程实现一个简单的，非标准的，基于UDP进行通信的Ping程序。
        学生需要用Python编写一个Ping客户端。
        客户端程序发送一个ping报文，然后接收一个从已经提供的服务器上返回的对应pong报文，
        并计算出从该客户发送ping报文到接收到pong报文为止的往返时延
        （Round-Trip Time，RTT）。

  \item 在客户端程序一次执行过程中，
        学生编写的的Ping客户端程序需经UDP向服务器发送10个ping报文。
        对于每个报文，当对应的pong报文返回时，客户端程序要确认并打印输出RTT值；
        在整个执行过程中，客户端程序需要考虑分组丢失情况，
        客户端最多等待1秒，超过该时长则打印丢失报文。
\end{enumerate}

\subsection{实验原理、方法和手段}
\label{subsec:c:socket:s:udp_principle}

UDP作为一种传输层协议，只提供了无连接通信，且不对传送的数据包进行可靠性保证，
因此只适合于一次传输少量数据的应用场景，
如果在传输过程中需要保证可靠性，则这种可靠性应该由应用层负责。
本实验创建的Ping程序正是一种不需要保证可靠性的程序，
并需要利用这种不可靠性来测量网络的联通情况。

虽然UDP不保证通信的可靠性，包到达的顺序，也不提供流量控制。
但正是因为UDP的控制选项较少，所以在数据传输过程中延迟小、数据传输效率高，
一些对可靠性要求不高，但对性能等开销更敏感的应用层协议会选择基于UDP进行实现，
常见的使用UDP的应用层协议包括TFTP、SNMP、NFS、DNS、BOOTP等，
通常占用53（DNS）、69（TFTP）、161（SNMP）等端口。

基于UDP的无连接客户/服务器在Python实现中的工作流程如下：

\begin{enumerate}
  \item 首先在服务器端通过调用\texttt{socket()}创建套接字来启动一个服务器；
  \item 服务器调用\texttt{bind()}指定服务器的套接字地址，
  然后调用\texttt{recvfrom()}等待接收数据。
  \item 在客户端调用\texttt{socket()}创建套接字，
  然后调用\texttt{sendto()}向服务器发送数据。
  \item 服务器接收到客户端发来的数据后，调用\texttt{sendto()}向客户发送应答数据，
  \item 客户调用\texttt{recvfrom()}接收服务器发来的应答数据。
  \item 一旦数据传输结束，服务器和客户通过调用\texttt{close()}来关闭套接字。
\end{enumerate}

注意在不同的计算机语言实现中，上述调用的名字和具体工作流程可能略有不同。
基于Python的UDP程序工作详细流程如图\ref{fig:c:socket_udp-flow}所示。

\begin{figure}[!ht]
  \centering
  \includegraphics[width=8cm]{udp-flow}
  \caption{无连接客户/服务器流程图}
  \label{fig:c:socket_udp-flow}
\end{figure}

基于Python进行UDP消息的接收操作时，Python程序将工作在阻塞状态，
即未收到数据包时，Python程序将挂起等待而不会继续执行。
如果程序运行中网络连接出现了问题，导致数据包无法及时到达，
这种阻塞式的工作模式将会严重的干扰程序的执行。
为了解决这个问题，Python的套接字通信库提供了一种“超时”机制来防止程序卡死。
在Python套接字程序中，
套接字对象提供了一个\texttt{settimeout()}方法来限制\texttt{recvfrom()}函数的等待时间，
当\texttt{recvfrom()}函数阻塞等待超过这个时间（一般称为“超时时间”）后仍然没有收到数据时，
程序将会抛出一个异常来说明发生了等待数据接收超时事件。
在编写Python网络通信程序时，可以利用这个机制来判断是否接收数据超时。

\subsection{实验条件}
\label{subsec:c:socket:s:udp_requirement}

\begin{itemize}
  \item 装有python环境的电脑两台；
  \item 局域网环境；
  \item 服务器程序（\nameref{subsec:c:socket:s:udp_additional}中已给出）；
  \item Python语言参考手册 -- UDP部分\footnote{可以参考Python3官方手册的
    \href{https://docs.python.org/zh-cn/3/library/socket.html}
    {套接字}部分，也可以查询其他相关手册}。
\end{itemize}

\subsection{实验步骤}
\label{subsec:c:socket:s:udp_procedure}

本实验\nameref{subsec:c:socket:s:udp_additional}一节中展示了一段Python代码，
实现了一个UDP服务器，该服务器还会模拟丢失30\%的客户端数据包。
请参考该代码，基于Python按照实验任务要求完成Ping程序的客户端。

注意：在运行客户端程序前，需要先运行服务器端代码。

编写成功后，使用客服端Ping程序经UDP向目标服务器发送10个ping报文。要求：

\begin{enumerate}
  \item 使用UDP发送ping消息（注意：因为UDP是无连接协议，不需要建立连接。）；
  \item 如果服务器在1秒内响应，则打印该响应消息；
  计算并打印每个数据包的往返时间RTT（以秒为单位）；
  \item 否则，打印“请求超时”（中英文皆可）。
\end{enumerate}

在开发过程中，可以将客户端程序和服务器程序放在同一台电脑上进行测试。
在完成代码调试后，可以尝试将客户端和服务器代码运行在不同网络环境，
记录并分析结果。

\subsection{进阶任务}
\label{subsec:c:socket:s:udp_rethink}

尝试修改代码，在程序运行结束时，计算所有ping消息的最小、最大和平均RTT，
并计算丢包率（丢失数据包在总数据包中所占有的百分比）。
即构造出一个符合标准Windows版Ping程序工作模式的基于UDP版Ping程序。

\subsection{考核方法}
\label{subsec:c:socket:s:udp_criterion}

本实验需提交一份实验报告和编写的代码文件。报告内容应当包括以下三个部分：
\begin{itemize}
  \item 代码的说明；
  \item 不同环境下代码运行的结果；
  \item 对结果的分析和总结体会。
\end{itemize}

本实验评分标准：

\begin{enumerate}
  \item 规定时间内完成实验报告20分；
  \item 代码正确运行，20分（不能正常运行0分）；
  \item 实验报告格式整洁，20分；
  \item 实验报告中详细记录了实验过程，在实验中所遇到的问题以及解决方法，20分；
  \item 实验报告中仔细分析了实验结果，并能提出自己的改进措施，20分。  
\end{enumerate}

\subsection{附件}
\label{subsec:c:socket:s:udp_additional}

基于Python的UDP套接字示例程序：

\begin{code}[python]
  from socket import *
  # 创建一个UDP套接字（SOCK_DGRAM）
  serverSocket = socket(AF_INET, SOCK_DGRAM) 
  serverSocket.bind(('', 12000))
  while True:
      rand = random.randint(0, 10)
      message, address = serverSocket.recvfrom(1024)
      message = message.upper()
      # 模拟30%的数据包丢失。
      if rand < 4:
          Continue
      serverSocket.sendto(message, address)
\end{code}
