|
|
%# -*- coding: utf-8-unix -*-
|
|
|
|
|
|
\section{TCP通信与Web服务器}
|
|
|
\label{sec:c:socket:s:web}
|
|
|
|
|
|
\subsection{实验目的}
|
|
|
\label{subsec:c:socket:s:web_object}
|
|
|
|
|
|
熟悉基于Python进行TCP套接字编程的基础知识,理解HTTP报文格式,
|
|
|
能基于Python编写一个可以一次响应一个HTTP请求,
|
|
|
并返回静态文件的简单Web服务器。
|
|
|
|
|
|
\subsection{实验内容}
|
|
|
\label{subsec:c:socket:s:web_content}
|
|
|
|
|
|
利用Python开发一个可以一次处理一个HTTP请求的Web服务器,
|
|
|
该服务器可以接受并解析HTTP请求,
|
|
|
然后从服务器的文件系统中读取被HTTP请求的文件,
|
|
|
并根据该文件是否存在而向客户端发送正确的响应消息,
|
|
|
|
|
|
\subsection{实验原理、方法和手段}
|
|
|
\label{subsec:c:socket:s:web_principle}
|
|
|
|
|
|
基于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{面向连接客户/服务器流程图}
|
|
|
\label{fig:c:socket_tcp-flow}
|
|
|
\end{figure}
|
|
|
|
|
|
\subsection{实验条件}
|
|
|
\label{subsec:c:socket:s:web_requirement}
|
|
|
|
|
|
\begin{itemize}
|
|
|
\item 装有python环境的电脑两台;
|
|
|
\item 局域网环境;
|
|
|
\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}
|
|
|
|
|
|
本实验\nameref{subsec:c:socket:s:web_additional}一节中展示了一份不完整的Web服务器框架代码,
|
|
|
学生需要逐步填充代码中不完善的部分,并完成一个具有以下功能的简单Web服务器:
|
|
|
\begin{enumerate}
|
|
|
\item 服务器收到请求时能创建一个TCP套接字;
|
|
|
\item 可以通过这个TCP套接字接收HTTP请求;
|
|
|
\item 解析HTTP请求并在操作系统中确定客户端所请求的特定文件;
|
|
|
\item 从服务器的文件系统读取客户端请求的文件;
|
|
|
\item 当被请求文件存在时,创建一个由被请求的文件组成的“请求成功”HTTP响应报文;
|
|
|
\item 当被请求文件不存在时,创建“请求目标不存在”HTTP响应报文;
|
|
|
\item 通过TCP连接将响应报文发回客户端;
|
|
|
\end{enumerate}
|
|
|
|
|
|
在开发过程中,可以使用浏览器访问运行在同一台电脑上的服务器程序进行测试。
|
|
|
在完成代码调试后,可以尝试在不同主机上的使用浏览器发送请求测试服务器,分析并记录结果。
|
|
|
|
|
|
\subsection{进阶任务}
|
|
|
\label{subsec:c:socket:s:web_rethink}
|
|
|
|
|
|
本实验中的Web服务器一次只能处理一个HTTP请求,请自行查阅线程知识,
|
|
|
修改代码,实现一个能够同时处理多个请求的多线程服务器。
|
|
|
|
|
|
\subsection{注意事项及有关说明}
|
|
|
\label{subsec:c:socket:s:web_notice}
|
|
|
|
|
|
客户端发来的HTTP请求中,URL都是相对根“/”的相对路径,
|
|
|
因此需要将服务器文件系统中的某个地址映射为这个“根”。
|
|
|
为了简化工作,建议将服务器程序存放的路径作为这个根,
|
|
|
这样运行服务器程序时,程序会自动从当前运行的路径开始查询文件。
|
|
|
|
|
|
例如:
|
|
|
假设将“HelloWorld.html”这个html文件放置在服务器程序文件存放目录中,
|
|
|
服务器运行主机的IP地址为“\texttt{123.234.12.34}”,
|
|
|
6789为服务器监听的端口号。
|
|
|
|
|
|
则从URL:\url{http://123.234.12.34:6789/HelloWorld.html}
|
|
|
出发可以获取到“HelloWorld.html”这个文件。
|
|
|
|
|
|
\subsection{考核方法}
|
|
|
\label{subsec:c:socket:s:web_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: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}
|