%# -*- 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}
