# 实现一个轻量级的 Web 服务器
## 实验目的
深入掌握 HTTP 协议规范,学习如何编写标准的互联网应用服务器。
## 实验内容
服务程序能够正确解析 HTTP 协议,并传回所需的网页文件和图片文件
使用标准的浏览器,如 IE、Chrome 或者 Safari,输入服务程序的 URL 后,能够正常显示服务器上的网页文件和图片
服务端程序界面不做要求,使用命令行或最简单的窗体即可
功能要求如下:
- 服务程序运行后监听在 80 端口或者指定端口
- 接受浏览器的 TCP 连接(支持多个浏览器同时连接)
- 读取浏览器发送的数据,解析 HTTP 请求头部,找到感兴趣的部分
- 根据 HTTP 头部请求的文件路径,打开并读取服务器磁盘上的文件,以 HTTP 响应格式传回浏览器。要求按照文本、图片文件传送不同的 Content-Type,以便让浏览器能够正常显示。
- 分别使用单个纯文本、只包含文字的 HTML 文件、包含文字和图片的 HTML 文件进行测试,浏览器均能正常显示。
本实验可以在前一个 Socket 编程实验的基础上继续,也可以使用第三方封装好的
TCP 类进行/网络数据的收发
本实验要求不使用任何封装 HTTP 接口的类库或组件,也不使用任何服务端脚本程
序如 JSP、ASPX、PHP 等
本实验可单独完成或组成两人小组。若组成小组,则一人负责编写服务器 GET 方法的响应,另一人负责编写 POST 方法的响应和服务器主线程。
### 主要仪器设备
联网的 PC 机、Wireshark 软件、Visual Studio、gcc 或 Java 集成开发环境。
## 操作方法与实验步骤
阅读 HTTP 协议相关标准文档,详细了解 HTTP 协议标准的细节,有必要的话使用
Wireshark 抓包,研究浏览器和 Web 服务器之间的交互过程
创建一个文档目录,与服务器程序运行路径分开
准备一个纯文本文件,命名为 test.txt,存放在 txt 子目录下面准备好一个图片文件,命名为 logo.jpg,放在 img 子目录下面
写一个 HTML 文件,命名为 test.html,放在 HTML 子目录下面,主要内容为:
```
<html>
<head><title>Test</title></head>
<body>
<h1>This is a test</h1>
<img src="img/logo.jpg">
<form action="dopost" method="POST">
Login:<input name="login">
Pass:<input name="pass">
<input type="submit" value="login">
</form>
</body>
</html>
```
将 test.html 复制为 noimg.html,并删除其中包含 img 的这一行。
服务端编写步骤(需要采用多线程模式)
运行初始化,打开 Socket,监听在指定端口(请使用学号的后 4 位作为服务器的监听端口)
主线程是一个循环,主要做的工作是等待客户端连接,如果有客户端连接成功,为该客户端创建处理子线程。该子线程的主要处理步骤是:
不断读取客户端发送过来的字节,并检查其中是否连续出现了 2 个回车换行符,如果未出现,继续接收;如果出现,按照 HTTP 格式解析第 1 行,分离出方法、文件和路径名,其他头部字段根据需要读取。
## 如果解析出来的方法是 GET
根据解析出来的文件和路径名,读取响应的磁盘文件(该路径和服务器程序可能不在同一个目录下,需要转换成绝对路径)。如果文件不存在,第 3 步的响应消息的状态设置为 404,并且跳过第 5 步。
准备好一个足够大的缓冲区,按照 HTTP 响应消息的格式先填入第 1 行(状态码=200),加上回车换行符。然后模仿 Wireshark 抓取的 HTTP 消息,填入必要的几行头部(需要哪些头部,请试验),其中不能缺少的 2 个头部是 Content-Type 和 Content-Length。Content-Type 的值要和文件类型相匹配(请通过抓包确定应该填什么),Content-Length 的值填写文件的字节大小。
在头部行填完后,再填入 2 个回车换行
将文件内容按顺序填入到缓冲区后面部分。
## 如果解析出来的方法是 POST
检查解析出来的文件和路径名,如果不是 dopost,则设置响应消息的状态为 404,然后跳到第 9 步。如果是 dopost,则设置响应消息的状态为 200,并继续下一步。
读取 2 个回车换行后面的体部内容(长度根据头部的 Content-Length 字段的指示),并提取出登录名(login)和密码(pass)的值。如果登录名是你的学号,密码是学号的后 4 位,则将响应消息设置为登录成功,否则将响应消息设置为登录失败。
将响应消息封装成 HTML 格式,如
```
<html><body>响应消息内容</body></html>
```
准备好一个足够大的缓冲区,按照 HTTP 响应消息的格式先填入第 1 行(根据前面的情况设置好状态码),加上回车换行符。然后填入必要的几行头部,其中不能缺少的 2 个头部是 Content-Type 和Content-Length。Content-Type 的值设置为 text/html,如果状态码=200,则 Content-Length 的值填写响应消息的字节大小,并将响应消息填入缓冲区的后面部分,否则填写为 0。
最后一次性将缓冲区内的字节发送给客户端。
发送完毕后,关闭 socket,退出子线程。
主线程还负责检测退出指令(如用户按退出键或者收到退出信号),检测到后即通知并等待各子线程退出。最后关闭 Socket,主程序退出。
编程结束后,将服务器部署在一台机器上(本机也可以)。在服务器上分别放置纯文本文件(.txt)、只包含文字的测试 HTML 文件(将测试 HTML 文件中的包含 img 那一行去掉)、包含文字和图片的测试 HTML 文件(以及图片文件)各一个。
确定好各个文件的 URL 地址,然后使用浏览器访问这些 URL 地址,如 http://x.x.x.x:port/dir/a.html,其中 port 是服务器的监听端口,dir 是提供给外部访问的路径,请设置为与文件实际存放路径不同,通过服务器内部映射转换。
检查浏览器是否正常显示页面,如果有问题,查找原因,并修改,直至满足要求使用多个浏览器同时访问这些 URL 地址,检查并发性
## 实验数据记录和处理
请将以下内容和本实验报告一起打包成一个压缩文件上传:
源代码:需要说明编译环境和编译方法,如果不能编译成功,将影响评分
可执行文件:可运行的.exe 文件或 Linux 可执行文件 服务器的主线程循环关键代码截图(解释总体处理逻辑,省略细节部分)
通过阻塞调用 accept 进行监听客户端的消息,确认连接后开启子线程,接收客户端发送的消息并对其进行处理。
![](https://www.writebug.com/myres/static/uploads/2022/9/6/ca31495bf0f625f4c939ec03c603bd72.writebug)
服务器的客户端处理子线程关键代码截图(解释总体处理逻辑,省略细节部分)
通过解析客户端发来的信息,确定请求类型是 GET 还是 POST,根据请求类型来处理信息。
![](https://www.writebug.com/myres/static/uploads/2022/9/6/3c9fda85c1e4619786f38877099c2042.writebug)
服务器运行后,用 netstat –an 显示服务器的监听端口
![](https://www.writebug.com/myres/static/uploads/2022/9/6/7103cc2c985e4ec67d0fcbc62c123756.writebug)
浏览器访问纯文本文件(.txt)时,浏览器的 URL 地址和显示内容截图。
![](https://www.writebug.com/myres/static/uploads/2022/9/6/011b1ba510f7e77dfcb5940b25c20e7f.writebug)
服务器上文件实际存放的路径:
![](https://www.writebug.com/myres/static/uploads/2022/9/6/857a97c525dfdaa0ff97eed5dea92c54.writebug)
服务器的相关代码片段�