From 263d4c362976b3385de961e820c1a76d91d6f8af Mon Sep 17 00:00:00 2001 From: Xphi Date: Fri, 5 Mar 2021 01:11:43 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=96=B0=E6=95=B4=E7=90=86=E7=AC=AC?= =?UTF-8?q?=E4=BA=8C=E5=8D=95=E5=85=83=E6=96=87=E5=AD=97=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E6=A0=BC=E5=BC=8F=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/main.tex | 1 + data/appendix/cheat_sheet.tex | 7 +- data/ch_socket/preface.tex | 12 ++- data/ch_socket/sec_smtp.tex | 173 +++++++++++++++++++++------------- data/ch_socket/sec_udp.tex | 152 +++++++++++++++++------------ data/ch_socket/sec_web.tex | 170 +++++++++++++++++++-------------- data/copyright.tex | 5 +- 7 files changed, 316 insertions(+), 204 deletions(-) diff --git a/config/main.tex b/config/main.tex index 85617bf..fcf197a 100644 --- a/config/main.tex +++ b/config/main.tex @@ -11,6 +11,7 @@ \usepackage{newtxtext} \usepackage{amsmath,mathtools,amsthm,amsfonts,amssymb,bm} \usepackage{titlesec} +\usepackage[perpage]{footmisc} \usepackage[colorlinks,bookmarksnumbered,urlcolor=blue, linkcolor=purple,citecolor=red,plainpages=false]{hyperref} \usepackage[centering,a4paper,body={16cm,22cm}]{geometry} diff --git a/data/appendix/cheat_sheet.tex b/data/appendix/cheat_sheet.tex index 62788ab..4f8b651 100644 --- a/data/appendix/cheat_sheet.tex +++ b/data/appendix/cheat_sheet.tex @@ -189,16 +189,17 @@ \multirow{3}{1.5cm}{基于套接字的网络程序设计} & 套接字基础与UDP通信& - 使用Python编写一个简单的,非标准的基于UDP的ping程序,该程序能够发送简单的ping报文,接收从服务器往返的对应pong报文,并确定从该客户发送ping报文到接收pong报文为止的时延(往返时延RTT)。& + 使用Python编写一个简单的,非标准的基于UDP的Ping程序,该程序能够发送简单的ping报文, + 接收从服务器往返的对应pong报文,并确定从该客户发送ping报文到接收pong报文为止的往返时延。& 根据实验指导书,参考服务器端代码,完成客户端代码,测试和分析实验结果。\\ \cline{2-4} & TCP通信与Web服务器 & 使用Python开发一个一次能处理一个HTTP请求的Web服务器。& - 根据实验指导书,在提供的框架基础上完成客户端代码,测试和分析实验结果。\\ \cline{2-4} + 根据实验指导书,在提供的框架基础上完成Web服务器代码,测试和分析实验结果。\\ \cline{2-4} & SMTP客户端实现 & 使用Python开发一个能够发送文本信息的简单的SMTP客户端。& - 根据实验指导书,在提供的框架基础上完成客户端代码,测试和分析实验结果。\\ \hline + 根据实验指导书,在提供的框架基础上完成SMTP客户端代码,测试和分析实验结果。\\ \hline \multirow{3}{1.5cm}{组网基础} & diff --git a/data/ch_socket/preface.tex b/data/ch_socket/preface.tex index 88302a5..1011620 100644 --- a/data/ch_socket/preface.tex +++ b/data/ch_socket/preface.tex @@ -3,7 +3,11 @@ \chapter{基于套接字的网络程序设计} \label{chap:program} -本单元通过编写简单的UDP应用程序和适配常见应用层协议的TCP应用程序使学生能熟练掌握基本套接字编程, -熟悉HTTP与SMTP等应用层协议, -从而深入理解理解跨网络进程通信的原理和过程, -培养开发网络应用程序的能力。 \ No newline at end of file +本单元实验要求学生能熟练掌握基本套接字编程。 +通过让学生编写简单的UDP应用程序、 +实现适配常见应用层协议的TCP应用程序来熟悉HTTP与SMTP等应用层协议, +从而深入理解跨网络进程通信的原理和过程, +培养开发网络应用程序的能力。 + +本单元实验设计思路与部分框架代码来自于J.F Kurose与K.W. Ross编纂的 +《计算机网络自顶向下方法(第7版)》中“套接字编程”配套材料。 diff --git a/data/ch_socket/sec_smtp.tex b/data/ch_socket/sec_smtp.tex index 4d43058..a9892fc 100644 --- a/data/ch_socket/sec_smtp.tex +++ b/data/ch_socket/sec_smtp.tex @@ -6,87 +6,147 @@ \subsection{实验目的} \label{subsec:c:socket:s:smtp_object} -进一步理解和掌握Python中TCP套接字编程的基础知识, -理解SMTP报文格式,了解开发一个简单应用程序的流程。 +进一步理解和掌握基于Python进行TCP套接字编程的知识, +理解SMTP报文格式,能基于Python编写一个简单的SMTP客户端程序。 \subsection{实验内容} \label{subsec:c:socket:s:smtp_content} -创建一个可以向任何接收方发送电子邮件的简单邮件客户端。 -通过Python编写代码与邮件服务器创建一个TCP连接, -使用SMTP协议与邮件服务器交谈并发送邮件报文,最后关闭连接。 +通过Python编写代码创建一个可以向标准电子邮件地址发送电子邮件的简单邮件客户端。 +该客户端可以与邮件服务器创建一个TCP连接, +并基于SMTP协议与邮件服务器交互并发送邮件报文, +完成邮件发送后关闭连接。 \subsection{实验原理、方法和手段} \label{subsec:c:socket:s:smtp_principle} -SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议, -是因特网电子邮件中主要的应用层协议,它使用TCP可靠数据传输服务, +简单邮件传输协议(Simple Mail Transfer Protocol,SMTP) +是实现电子邮件收发的主要应用层协议,它基于TCP提供的可靠数据传输连接, 从发送方的邮件服务器向接收方的邮件服务器发送邮件。 -SMTP有两个部分:运行在发送方邮件服务器的客户端和运行在接收方邮件服务器的服务器端。 - -RFC5321给出了SMTP的定义。SMTP有众多出色的性质, -但也具有一些陈旧特征, +注意,虽然一般情况下邮件总是从发送方的邮件服务器中发出, +但是工作在发送方邮件服务器上的发送程序是一个SMTP客户端, +因此一个完整的SMTP程序总有两个部分参与工作: +运行在发送方邮件服务器的SMTP客户端和运行在接收方邮件服务器的SMTP服务器。 + +SMTP是一个古老的应用层协议, +1982年在RFC文档 \href{https://tools.ietf.org/html/rfc821}{821} +中首次被定义, +然后在2001和2008年进行了两次更新, +分别为RFC \href{https://tools.ietf.org/html/rfc2821}{2821} +和RFC \href{https://tools.ietf.org/html/rfc5321}{5321}。 +因此虽然SMTP拥有众多出色的性质,但也遗留了一些陈旧特征, 例如,它限制所有邮件报文的主体部分只能采用简单的7比特ASCII码表示。 -所以在用SMTP传送邮件之前需要将二进制多媒体数据编码为ASCII码, -在传输后再进行解码还原为多媒体数据。 - -下来分析一个在SMTP客户(C)和SMTP服务器(S)之间交换报文文本的例子。 -客户的主机名为crepes.fr,服务器的主机名为hamburger.edu。 -以C:开头的ASCI码文本行正是客户交给其TCP套接字的那些行, -以S:开头的ASCl码则是服务器发送给其TCP套接字的那些行。 -一旦创建了TCP连接,就开始了如下过程: +所以在用SMTP传送邮件之前需要将二进制数据编码为ASCII码, +在传输后再进行解码还原为二进制数据 +\footnote{SMTP存在一些服务扩展定义, +例如RFC \href{https://tools.ietf.org/html/rfc3030}{3030} +就定义了如何采用非ASCII的方式传送二进制数据}。 + +假设存在一个发送方邮件服务器,主机名为company.com; +而对应的接收方邮件服务器的主机名为network.net。 +SMTP客户端要从地址alice@company.com向地址bob@network.net发送报文 +\texttt{Do you like ketchup?How about pickles?}。 +以下流程展示了运行在发送方邮件服务器上的SMTP客户端(C) +和运行在接收方邮件服务器上的SMTP服务器(S)之间交换SMTP报文文本的实际通信过程: \begin{code}[text] - S: 220 hamburger. edu - C: HELO crepes. fr - S: 250 Hello crepes. fr, pleased to meet you - C: MAIL FROM: - S: 250 alice@ crepes. fr... Sender ok - C: RCPT TO: - S: 250 bob@ hamburger. edu... Recipient ok + S: 220 network.net + C: HELO company.com + S: 250 Hello company.com, pleased to meet you + C: MAIL FROM: + S: 250 alice@company.com... Sender ok + C: RCPT TO: + S: 250 bob@network.net... Recipient ok C: DATA S:354 Enter mail, end with "."on a line by itself C: Do you like ketchup? C: How about pickles? C: . S: 250 Message accepted for delivery C: QUIT - S: 221 hamburger. edu closing connection + S: 221 network.net closing connection \end{code} -在上例中,客户从邮件服务器crepes.fr向邮件服务器hamburger.edu发送了一个报文 -("Do you like ketchup?How about pickles?")。 -作为对话的一部分, -该客户发送了5条命令:HELO(是HELLO的缩写)、MAIL FROM、RCPTTO、DATA以及QUIT。 -这些命令都是自解释的。 -该客户通过发送一个只包含一个句点的行,向服务器指示该报文结束了。 -(按照ASClI码的表示方法,每个报文以CRLF.CRLF结束,其中的CR和LF分别表示回车和换行。) -服务器对每条命令做出回答,其中每个回答含有一个回答码和一些(可选的)英文解释。 -我们在这里指出SMTP用的是持续连接: -如果发送邮件服务器有几个报文发往同一个接收邮件服务器, -它可以通过同一个TCP连接发送这些所有的报文。 -对每个报文,该客户用一个新的MAIL FROM:crepes.fr开始, -用一个独立的句点指示该邮件的结束,并且仅当所有邮件发送完后才发送QUIT。 +注意:以\texttt{C:}开头的ASCII码文本行是SMTP客户端通过TCP套接字发出的消息, +而以\texttt{S:}开头的ASCII码则是SMTP服务器通过TCP套接字发出的消息。 + +在这个例子中,SMTP客户端发送了5条命令: +\texttt{HELO}(是HELLO的缩写)、\texttt{MAIL FROM}、 +\texttt{RCPT TO}、\texttt{DATA}以及\texttt{QUIT}, +这些命令都是自解释的,可以简单通过其英文含义理解。 +在具体消息内容部分,客户端通过发送一个只包含一个句点的行向服务器指示消息内容的结束 +(注意,在实际传输时,报文在结尾处会包含两个额外的不可打印字符:“0x0A”与“0x0D”。 +分别表示回车“CR”和换行“LF”)。 +服务器对客户端发出的每条命令都做出了回答, +其中每个回答含有一个回答码和一些英文解释, +其中的英文解释内容在实际使用时是可选的。 + +有一点需要注意,由于SMTP使用的是TCP连接,所以可以复用一个连接发送多封邮件。 +在这种情况下,发送方在完成握手后会连续发送所有这些邮件, +对每个邮件,客户端用一个新的\texttt{MAIL FROM:}开始, +并用一个独立的句点指示该邮件的结束, +然后在所有邮件发送完后才发送QUIT结束一次与服务器的连接。 \subsection{实验条件} \label{subsec:c:socket:s:smtp_requirement} \begin{itemize} - \item 下列装有python环境的电脑一台; - \item 邮件服务器账号(开启SMTP); - \item 部分代码(实验步骤中已给出)。 + \item 装有python环境的电脑一台; + \item 已经正常运行的邮件服务器(开启非加密模式的SMTP支持); + \item 部分代码(\nameref{subsec:c:socket:s:smtp_additional}中已给出); + \item Python语言参考手册 -- TCP部分\footnote{可以参考Python3官方手册的 + \href{https://docs.python.org/zh-cn/3/library/socket.html} + {套接字}部分,也可以查询其他相关手册}; + \item SMTP协议参考手册 + \footnote{可以参考SMTP的\href{https://tools.ietf.org/html/rfc5321}{RFC文档}, + 也可以查询其他相关手册}。 \end{itemize} -参考资料: +\subsection{实验步骤} +\label{subsec:c:socket:s:smtp_procedure} + +本实验\nameref{subsec:c:socket:s:smtp_additional} +一节中展示了一段SMTP客户端的框架代码, +学生需要逐步填充代码中不完善的部分,完成一个简单电子邮件客户端程序, +并通过向不同的账号发送电子邮件来测试程序。 + +\subsection{进阶任务} +\label{subsec:c:socket:s:smtp_rethink} + +阅读相关文档,修改代码,使编写的程序可以发送包含图片等二进制数据的电子邮件。 + +\subsection{注意事项及有关说明} +\label{subsec:c:socket:s:smtp_notice} + +由于SMTP协议较为传统,因此部分公开的邮件服务器默认情况下关闭了SMTP支持, +有些邮件服务器为了安全只支持工作在TLS安全连接上的SMTP。 +因此为了方便测试,请选择支持非加密SMTP连接的服务器进行测试, +如果选择自行搭建SMTP服务器进行测试, +则需注意在设置中开启SMTP协议,并保证其工作在非TLS加密模式下。 + +\subsection{考核方法} +\label{subsec:c:socket:s:smtp_criterion} +本实验需提交一份实验报告和编写的代码文件。报告内容应当包括以下三个部分: \begin{itemize} - \item {J.F Kurose and K.W. Ross, 计算机网络自顶向下方法(第7版)} + \item 代码的说明; + \item 不同环境下代码运行的结果; + \item 对结果的分析和总结体会。 \end{itemize} -\subsection{实验步骤} -\label{subsec:c:socket:s:smtp_procedure} +本实验评分标准: + +\begin{enumerate} + \item 规定时间内完成实验报告20分; + \item 代码正确运行,20分(不能正常运行0分); + \item 实验报告格式整洁,20分; + \item 实验报告中详细记录了实验过程,在实验中所遇到的问题以及解决方法,20分; + \item 实验报告中仔细分析了实验结果,并能提出自己的改进措施,20分。 +\end{enumerate} + +\subsection{附件} +\label{subsec:c:socket:s:smtp_additional} -通过Python开发一个简单的SMTP客户端发送邮件。 -补充完善下面的代码并通过向不同的账号发送电子邮件测试程序。 +基于Python的SMTP客户端框架程序: \begin{code}[python] from socket import * @@ -115,18 +175,3 @@ RFC5321给出了SMTP的定义。SMTP有众多出色的性质, # 消息以单个"."结束。 # 发送QUIT命令,获取服务器响应。 \end{code} - -\subsection{思考题} -\label{subsec:c:socket:s:smtp_rethink} - -修改代码使程序发送的邮件中不仅包含文本还能够包含图片。 - -\subsection{注意事项及有关说明} -\label{subsec:c:socket:s:smtp_notice} - -注意部分邮件服务器默认关闭SMTP,需进入设置手动开启SMTP协议。 - -\subsection{考核方法} -\label{subsec:c:socket:s:smtp_criterion} - -同实验\ref{sec:c:socket:s:udp}。 diff --git a/data/ch_socket/sec_udp.tex b/data/ch_socket/sec_udp.tex index 43918b0..56c68ac 100644 --- a/data/ch_socket/sec_udp.tex +++ b/data/ch_socket/sec_udp.tex @@ -6,43 +6,59 @@ \subsection{实验目的} \label{subsec:c:socket:s:udp_object} -熟悉Python中UDP套接字编程的基础知识,掌握使用UDP套接字发送和接收数据包, +熟悉基于Python进行UDP套接字编程的基础知识,掌握使用UDP套接字发送和接收数据包, 以及设置正确的套接字超时, -了解Ping应用程序及其在计算数据包丢失率等统计数据方面的有用性。 +了解Ping应用程序的基本概念,并理解其在简单判断网络状态, +例如计算数据包丢失率等统计数据方面的意义。 \subsection{实验内容} \label{subsec:c:socket:s:udp_content} -标准的ping使用ICMP,该实验创建一个简单的, -非标准的基于UDP的ping程序。用Python编写一个客户ping程序。 -该客户将发送一个简单的ping报文,接收一个从服务器往返的对应pong报文, -并确定从该客户发送ping报文到接收pong报文为止的时延(往返时延RTT)。 - -要求你的ping程序经UDP向目标服务器发送10个ping报文。 -对于每个报文,当对应的pong报文返回时,客户端要确认和打印RTT; -要求考虑分组丢失情况,客户端最多等待1秒,超过该时长则打印丢失报文。 +\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提供了无连接通信,且不对传送数据包进行可靠性保证, -适合于一次传输少量数据,UDP传输的可靠性由应用层负责。 -常用的UDP端口号有:53(DNS)、69(TFTP)、161(SNMP), -使用UDP协议包括:TFTP、SNMP、NFS、DNS、BOOTP。 - -UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。 -但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高, -适合对可靠性要求不高的应用程序, -或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。 - -基于UDP协议的无连接客户/服务器的工作流程是: -在服务器端首先启动服务器通过调用socket( )创建套接字, -然后调用bind( )指定服务器socket地址,最后服务器调用recvfrom( )等待接收数据。 -在客户端,客户调用socket( )创建套接字,然后调用sendto( )向服务器发送数据。 -服务器接收到客户发来数据后,调用sendto( )向客户发送应答数据, -客户调用recvfrom接收服务器发来的应答数据。 -一旦数据传输结束,服务器和客户通过调用close( )来关闭套接字。 -具体流程如图\ref{fig:c:socket_udp-flow}所示。 +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 @@ -51,68 +67,62 @@ UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠 \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 服务器程序(实验步骤中已给出)。 -\end{itemize} - - -参考资料: - -\begin{itemize} - \item {J.F Kurose and K.W. Ross, 计算机网络自顶向下方法(第7版)} + \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} -下面的服务器代码中30\%的客户端数据包被模拟为丢失。 -请参考该代码,按照实验任务完成ping客户端代码。 +本实验\nameref{subsec:c:socket:s:udp_additional}一节中展示了一段Python代码, +实现了一个UDP服务器,该服务器还会模拟丢失30\%的客户端数据包。 +请参考该代码,基于Python按照实验任务要求完成Ping程序的客户端。 -注意在运行客户端程序前,需要先运行服务器端代码。 +注意:在运行客户端程序前,需要先运行服务器端代码。 -使用客服端ping程序经UDP向目标服务器发送10个ping报文。要求: +编写成功后,使用客服端Ping程序经UDP向目标服务器发送10个ping报文。要求: \begin{enumerate} \item 使用UDP发送ping消息(注意:因为UDP是无连接协议,不需要建立连接。); \item 如果服务器在1秒内响应,则打印该响应消息; 计算并打印每个数据包的往返时间RTT(以秒为单位); - \item 否则,打印“请求超时”。 + \item 否则,打印“请求超时”(中英文皆可)。 \end{enumerate} 在开发过程中,可以将客户端程序和服务器程序放在同一台电脑上进行测试。 在完成代码调试后,可以尝试将客户端和服务器代码运行在不同网络环境, -记录并分析结果。下面是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() - # 模拟40%的数据包丢失。 - if rand < 4: - Continue - serverSocket.sendto(message, address) -\end{code} - -\subsection{思考题} +\subsection{进阶任务} \label{subsec:c:socket:s:udp_rethink} -尝试修改代码计算所有ping结束时报告最小、最大、平均rtt和计算丢包率(百分比) -以符合标准ping程序的工作方式。 +尝试修改代码,在程序运行结束时,计算所有ping消息的最小、最大和平均RTT, +并计算丢包率(丢失数据包在总数据包中所占有的百分比)。 +即构造出一个符合标准Windows版Ping程序工作模式的基于UDP版Ping程序。 \subsection{考核方法} \label{subsec:c:socket:s:udp_criterion} -本次实验需提交一份实验报告和代码文件。报告内容应当包括以下三个部分: +本实验需提交一份实验报告和编写的代码文件。报告内容应当包括以下三个部分: \begin{itemize} \item 代码的说明; \item 不同环境下代码运行的结果; @@ -128,3 +138,23 @@ UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠 \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} diff --git a/data/ch_socket/sec_web.tex b/data/ch_socket/sec_web.tex index 0626ed8..9a1ef46 100644 --- a/data/ch_socket/sec_web.tex +++ b/data/ch_socket/sec_web.tex @@ -6,33 +6,39 @@ \subsection{实验目的} \label{subsec:c:socket:s:web_object} -掌握Python中TCP套接字编程的基础知识,理解HTTP报文格式, -了解开发一个简单Web服务器的流程。 +熟悉基于Python进行TCP套接字编程的基础知识,理解HTTP报文格式, +能基于Python编写一个可以一次响应一个HTTP请求, +并返回静态文件的简单Web服务器。 \subsection{实验内容} \label{subsec:c:socket:s:web_content} -使用Python开发一个一次处理一个HTTP请求的Web服务器。 -Web服务器接受并解析HTTP请求,从服务器的文件系统中获取请求的文件, -创建一个HTTP响应消息,该消息由前面带有首部行信息的请求文件组成, -然后将响应直接发送给客户端。如果服务器中不存在请求的文件, -则服务器将发送HTTP“404 not Found”的消息给客户端。 +利用Python开发一个可以一次处理一个HTTP请求的Web服务器, +该服务器可以接受并解析HTTP请求, +然后从服务器的文件系统中读取被HTTP请求的文件, +并根据该文件是否存在而向客户端发送正确的响应消息, \subsection{实验原理、方法和手段} \label{subsec:c:socket:s:web_principle} -基于TCP协议的面向客户/服务器的工作流程是: -在服务器端首先启动服务器调用socket( )创建套接字; -然后调用bind( )指定服务器socket地址(IP地址+端口号); -再调用listen( )让服务器做好侦听准备,并规定好请求队列的长度, -然后服务器进入阻塞状态,等待客户的连接请求; -最后通过accept( )来接收连接请求,并获得客户的socket地址。 -在客户端,客户调用socket( )创建套接字,然后调用connect( )和服务器建立连接。 -连接建立成功后,客户和服务器之间就可以通过调用read( )和write( )来接收和发送数据。 -一旦数据传输结束,服务器和客户通过调用close( )来关闭套接字。 -具体流程图\ref{fig:c:socket_tcp-flow}所示。 - -\begin{figure}[!htp] +基于TCP的面向客户端/服务器在Python实现中的的工作流程是: +\begin{enumerate} + \item 首先在服务器端通过调用\texttt{socket()}创建套接字来启动一个服务器; + \item 服务器调用\texttt{bind()}绑定指定服务器的套接字地址(IP地址+端口号); + \item 服务器调用\texttt{listen()}做好侦听准备,同时规定好请求队列的长度; + \item 服务器进入阻塞状态,等待客户的连接请求; + \item 服务器通过\texttt{accept()}来接收连接请求,并获得客户的socket地址。 + \item 在客户端通过调用\texttt{socket()}创建套接字; + \item 客户端调用\texttt{connect()}和服务器建立连接。 + \item 连接建立成功后,客户端和服务器之间通过调用\texttt{read()} + 和\texttt{write()}来接收和发送数据。 + \item 数据传输结束后,服务器和客户各自通过调用\texttt{close()}关闭套接字。 +\end{enumerate} + +注意在不同的计算机语言实现中,上述调用的名字和具体工作流程可能略有不同。 +基于Python的TCP客户端/服务器具体工作流程如图\ref{fig:c:socket_tcp-flow}所示。 + +\begin{figure}[!ht] \centering \includegraphics[width=6cm]{tcp-flow} \caption{面向连接客户/服务器流程图} @@ -45,59 +51,35 @@ Web服务器接受并解析HTTP请求,从服务器的文件系统中获取请 \begin{itemize} \item 装有python环境的电脑两台; \item 局域网环境; - \item 部分代码(实验步骤中已给出)。 -\end{itemize} - -参考资料: - -\begin{itemize} - \item {J.F Kurose and K.W. Ross, 计算机网络自顶向下方法(第7版)} + \item 部分代码(\nameref{subsec:c:socket:s:web_additional}中已给出); + \item Python语言参考手册 -- TCP部分\footnote{可以参考Python3官方手册的 + \href{https://docs.python.org/zh-cn/3/library/socket.html} + {套接字}部分,也可以查询其他相关手册}; + \item HTTP协议参考手册\footnote{可以参考Mozilla提供的 + \href{https://developer.mozilla.org/zh-CN/docs/Web/HTTP/OverView} + {HTTP概述},或者HTTP 1.1版的\href{https://tools.ietf.org/html/rfc2616}{RFC文档}, + 也可以查询其他相关手册}。 \end{itemize} \subsection{实验步骤} \label{subsec:c:socket:s:web_procedure} -开发一个简单的Web服务器,一次处理一个请求,具体要求如下: +本实验\nameref{subsec:c:socket:s:web_additional}一节中展示了一份不完整的Web服务器框架代码, +学生需要逐步填充代码中不完善的部分,并完成一个具有以下功能的简单Web服务器: \begin{enumerate} - \item 当收到请求时创建一个连接套接字; - \item 通过这个连接套接字接收HTTP请求; - \item 解释HTTP请求以确定所请求的特定文件; - \item 从服务器的文件系统获得请求的文件; - \item 创建一个由请求的文件组成的HTTP响应报文; - \item 通过TCP连接返回响应。 + \item 服务器收到请求时能创建一个TCP套接字; + \item 可以通过这个TCP套接字接收HTTP请求; + \item 解析HTTP请求并在操作系统中确定客户端所请求的特定文件; + \item 从服务器的文件系统读取客户端请求的文件; + \item 当被请求文件存在时,创建一个由被请求的文件组成的“请求成功”HTTP响应报文; + \item 当被请求文件不存在时,创建“请求目标不存在”HTTP响应报文; + \item 通过TCP连接将响应报文发回客户端; \end{enumerate} -补充完善下面的的服务器代码,完成要求功能。 -运行服务器代码,在不同主机上的使用浏览器发送请求测试服务器,分析并记录结果。 -\begin{code}[python] - #import socket module - from socket import * - #准备服务器端socket - serverSocket = socket(AF_INET, SOCK_STREAM) - #按需补充代码 - while True: - #建立连接 - print 'Ready to serve...' - connectionSocket, addr = #按需补充 - try: - message = #按需补充 - filename = message.split()[1] - f = open(filename[1:]) - #通过socket发送HTTP首部行 - outputdata = #按需补充 - #将请求文件的内容发送到客户端 - for i in range(0, len(outputdata)): - connectionSocket.send(outputdata[i]) - connectionSocket.close() - except IOError: - #发送未找到文件的响应消息 - #按需补充代码 - #关闭客户端socket - #按需补充代码 - serverSocket.close() -\end{code} +在开发过程中,可以使用浏览器访问运行在同一台电脑上的服务器程序进行测试。 +在完成代码调试后,可以尝试在不同主机上的使用浏览器发送请求测试服务器,分析并记录结果。 -\subsection{思考题} +\subsection{进阶任务} \label{subsec:c:socket:s:web_rethink} 本实验中的Web服务器一次只能处理一个HTTP请求,请自行查阅线程知识, @@ -106,16 +88,64 @@ Web服务器接受并解析HTTP请求,从服务器的文件系统中获取请 \subsection{注意事项及有关说明} \label{subsec:c:socket:s:web_notice} -将HTML文件放在服务器代码同一目录中。运行服务器程序。 -确定运行服务器的主机的IP地址(例如,128.238.251.26)。 -从另一个主机打开浏览器并提供相应的URL。 +客户端发来的HTTP请求中,URL都是相对根“/”的相对路径, +因此需要将服务器文件系统中的某个地址映射为这个“根”。 +为了简化工作,建议将服务器程序存放的路径作为这个根, +这样运行服务器程序时,程序会自动从当前运行的路径开始查询文件。 -例如:\url{http://128.238.251.26:6789/HelloWorld.html}。 +例如: +假设将“HelloWorld.html”这个html文件放置在服务器程序文件存放目录中, +服务器运行主机的IP地址为“\texttt{123.234.12.34}”, +6789为服务器监听的端口号。 -“HelloWorld.html”是放置在服务器目录中的文件的名称。 -6789为端口号,如果省略则使用默认端口号80。 +则从URL:\url{http://123.234.12.34:6789/HelloWorld.html} +出发可以获取到“HelloWorld.html”这个文件。 \subsection{考核方法} \label{subsec:c:socket:s:web_criterion} -同实验\ref{sec:c:socket:s:udp}。 +本实验需提交一份实验报告和编写的代码文件。报告内容应当包括以下三个部分: +\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:web_additional} + +基于Python的Web服务器框架程序: + +\begin{code}[python] + #import socket module + from socket import * + # 准备服务器端socket(按需补充) + serverSocket = socket(AF_INET, SOCK_STREAM) + while True: + # 建立连接 + print 'Ready to serve...' + connectionSocket, addr = # 按需补充 + try: + message = # 按需补充 + filename = message.split()[1] + f = open(filename[1:]) + # 通过socket发送HTTP头部 + outputdata = #将请求文件的内容发送到客户端(按需补充) + for i in range(0, len(outputdata)): + connectionSocket.send(outputdata[i]) + connectionSocket.close() + except IOError: + # 发送未找到文件的响应消息(按需补充代码) + # 关闭客户端socket(按需补充代码) + serverSocket.close() +\end{code} diff --git a/data/copyright.tex b/data/copyright.tex index 96c10ce..4d80070 100644 --- a/data/copyright.tex +++ b/data/copyright.tex @@ -3,13 +3,14 @@ % 版本声明 \clearpage \begin{center} - \vspace{1em} + ~\\ + \vspace{2em} \Large{Version 2.0} \\ \vspace{2em} % \Large{发布日期: \today} \Large{2021 年 3 月 5 日} - \vspace{21em} + \vspace{18em} \renewcommand{\arraystretch}{0.6} \begin{tabular*}{0.85\textwidth}{rm{0.8\textwidth}}