0%

计网socket实验week1

一、协议设计

具体要求:

  1. 掌握课本有关 HTTP 的内容;阅读 HTTP/1.1 的标准文档 RFC2616[1];

  2. 搭建编程环境(参见“讲解 PPT-环境安装配置.pptx);

  3. 熟悉 Socket 编程方法;

  4. 掌握 lex 和 yacc[7]正确解析消息(message)的方法;

  5. 实现简单的 echo web server。

(一)代码源文件架构

文件目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
WEBSERVER

├── .vscode/
├── cgi/
├── include/
│ └── parse.h
├── obj/
├── samples/
├── src/
│ ├── echo_client.c
│ ├── echo_server.c
│ ├── example.c
│ ├── lex.yy.c
│ ├── lexer.l
│ ├── parse.c
│ ├── parser.y
│ ├── y.tab.c
│ ├── y.tab.h
│ ├── static_site/
│ ├── DockerFile
│ └── Makefile
└── README.md

代码源文件架构分析

  1. 根目录文件
    • README.md:包含项目的基本信息、使用方法和开发者指南。
    • Makefile:用于编译和链接项目中的各个源文件,自动化构建流程。
    • DockerFile:用于创建Docker镜像,提供一致的开发和部署环境。
  2. .vscode目录
    • 存放VS Code的配置文件,如任务、调试配置等。
  3. cgi目录
    • 用于存放CGI(Common Gateway Interface)相关的文件,实现动态网页生成。
  4. include目录
    • parse.h:头文件,声明解析器相关的函数和数据结构。
  5. obj目录
    • 存放编译生成的中间目标文件(object files),如.o文件。
  6. samples目录
    • 存放示例代码或测试样例。
  7. src目录
    • 源文件
      • echo_client.c:实现客户端代码,发送请求并接收服务器响应。
      • echo_server.c:实现服务器代码,接收并处理客户端请求,返回响应。
      • example.c:示例代码,用于展示如何使用某些功能或库。
      • lex.yy.c:由lexer.l生成的词法分析器代码。
      • lexer.l:词法分析器定义文件,定义如何将输入的文本流分解为标记(tokens)。
      • parse.c:解析器代码,包含语法分析的实现。
      • parser.y:语法分析器定义文件,使用Yacc或Bison生成解析器。
      • y.tab.c:由parser.y生成的语法分析器代码。
      • y.tab.h:由parser.y生成的语法分析器头文件,包含语法分析器使用的符号常量。
    • 静态站点文件
      • static_site/:存放静态网页文件,如HTML、CSS、JavaScript等。

功能模块分析

  1. 客户端模块(echo_client.c)
    • 实现客户端功能,主要负责发送HTTP请求到服务器并接收响应。
  2. 服务器模块(echo_server.c)
    • 实现服务器功能,主要负责接收和解析客户端请求,并返回相应的HTTP响应。
    • 支持处理GET, HEAD, POST等HTTP方法,并返回相应的数据。
  3. 词法分析模块(lexer.l -> lex.yy.c)
    • 使用词法分析器将输入的文本流分解为标记,供语法分析器使用。
  4. 语法分析模块(parser.y -> y.tab.c, y.tab.h)
    • 使用语法分析器解析输入的标记序列,生成对应的语法树或执行相应的操作。
    • 结合词法分析器,完成对HTTP请求的解析。
  5. 解析模块(parse.c, parse.h)
    • 实现具体的解析逻辑,包括解析HTTP头部和消息体。
    • 使用前面生成的词法和语法分析器,对输入消息进行全面解析。

消息解析方法

Lex 和 Yacc 介绍

  1. Lex(词法分析器生成器):
    • 作用:负责将输入的字符流分解成一个个标记(tokens),这些标记是语法分析器(Yacc)能够处理的基本单元。
    • 工作原理:
      1. 定义词法规则:在.l文件中定义词法规则,描述不同标记的模式。
      2. 生成词法分析器:使用Lex工具读取.l文件,生成相应的词法分析器代码(如lex.yy.c)。
      3. 标记识别:运行词法分析器代码,将输入的字符流转换成标记序列。
  2. Yacc(Yet Another Compiler-Compiler,语法分析器生成器):
    • 作用:负责根据词法分析器提供的标记序列,按照预定义的语法规则生成语法树,或执行相应的动作。
    • 工作原理:
      1. 定义语法规则:在.y文件中定义语法规则,描述标记的组合方式。
      2. 生成语法分析器:使用Yacc工具读取.y文件,生成相应的语法分析器代码(如y.tab.cy.tab.h)。
      3. 语法分析:运行语法分析器代码,解析标记序列,生成语法树或执行解析操作。

消息解析流程

  1. 词法分析(Lex):
    • 输入:HTTP请求的字符流。
    • 输出:标记序列(如方法名、URL、HTTP版本、头部字段名、字段值等)。
  2. 语法分析(Yacc):
    • 输入:Lex生成的标记序列。
    • 输出:解析后的语法树,或直接执行相应操作(如存储请求方法、路径、HTTP版本、头部字段等)。
  3. 请求处理:
    • 根据语法分析器解析的结果,处理不同的HTTP方法(GET、HEAD、POST)。
    • 对于未实现的方法,返回501 Not Implemented响应。
    • 对于格式错误的请求,返回400 Bad Request响应。

Lex 和 Yacc 工作流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
+--------------------------+
| HTTP 请求字符流 |
+--------------------------+
|
v
+--------------------------+
| Lex 词法分析 |
+--------------------------+
|
v
+--------------------------+
| 标记序列 |
+--------------------------+
|
v
+--------------------------+
| Yacc 语法分析 |
+--------------------------+
|
v
+--------------------------+
| 解析结果或语法树 |
+--------------------------+
|
v
+--------------------------+
| 请求处理与响应生成 |
+--------------------------+

二、协议实现

1.消息解析

在使用 ./example /samples/... 进行测试时,发现 start code 中的代码只能解析 request_line 和单个 request_head,无法进一步解析多条 request_head。因此,该代码仅能正确处理 sample_request_example

为了解析多条 request_head,我们需要修改 parser.y 中的 request_header 解析规则。关键在于正确使用 yacc 中的 | 符号,通过递归解析所有的 request_header

在消息解析部分,还有一个重要任务是在 parse.c 中处理畸形请求。在 parse.c 中,有一个 TODO 是关于处理畸形请求(Handle Malformed Requests)。

1
//TODO Handle Malformed Requests

为此,我们需要使用 yystart 语句来实现,并在解析完成后使用 memset 清空 buf

另外,代码中的 malloc 语句一开始只分配了处理一行的内存(malloc(sizeof(request_header) * 1)),为了能够解析多个 request_header,我们需要将其修改为 19 行(malloc(sizeof(request_header) * 19))。

1
request->headers = (Request_header *) malloc(sizeof(Request_header)*19);

具体修改为:

解析规则修改:在 parser.y 中,修改 request_header 的解析规则,使用 | 符号递归解析多条 request_header

处理畸形请求:在 parse.c 中,使用 yystart 语句处理畸形请求,并在解析完成后使用 memset 清空 buf

内存分配:将 malloc(sizeof(request_header) * 1) 修改为 malloc(sizeof(request_header) * 19),以便解析多个 request_header

2.实现服务端与客户端的通信

服务端与客户端的通信可以分为两个部分:echo_serverecho_client

1)echo_client

Start code 中的 echo_client 部分实现较为完善,但在读取报文时使用了 fgets 函数,这种参数传递方式并不理想。更好的方法是通过 open()argv[3] 中的内容读入 fd_in(文件描述符),然后通过 read()fd_in 和缓冲区写入 readRet

2)echo_server

echo_server 部分,需要实现对 GETHEADPOST 请求的 echo 功能,以及对未实现请求返回 501 状态码和格式错误请求返回 400 状态码。以下是状态码的相关描述:

  • 1xx: 报告的 - 接收到请求,继续处理。
  • 2xx: 成功 - 请求成功接收、理解并接受。
  • 3xx: 重定向 - 需要进一步操作以完成请求。
  • 4xx: 客户端错误 - 请求包含语法错误或无法完成。
  • 5xx: 服务器错误 - 服务器无法完成显然有效的请求。

这些状态码和原因短语一起为客户端提供了响应信息,客户端只需检查状态码,不必解析原因短语。

Start code 中的 echo_server 部分已经基本实现,现在需要修改的是服务器在发现还有未处理的报文时的行为。

伪代码实现

以下是服务端处理未处理报文时的伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
while ((readret = recv(client_sock, buf, BUF_SIZE, 0)) >= 1) {
request = parse(buf, readret, sock)

if request is NULL:
send(client_sock, "HTTP/1.1 400 Bad request\r\n\r\n", 50, 0)
else if request.http_method in ["POST", "HEAD", "GET"]:
send(client_sock, buf, readret, 0)
else:
send(client_sock, "HTTP/1.1 501 Not Implemented\r\n\r\n", 50, 0)

free(request.headers)
free(request)
memset(buf, 0, BUF_SIZE)
}

if readret == -1:
handle_error(client_sock, sock, "Error reading from client socket.")

if close_socket(client_sock):
handle_error(sock, sock, "Error closing client socket.")

为了改善 echo_client 的报文读取,使用 open()argv[3] 中的内容读入文件描述符 fd_in,然后通过 read()fd_in 和缓冲区写入 readRet

echo_server 部分,需要实现对 GETHEADPOST 请求的 echo 功能,并对未实现的请求返回 501 状态码,对格式错误的请求返回 400 状态码。

在服务器发现未处理报文时,首先通过 parse 函数解析报文,如果请求为空,向 client_sock 发送 "HTTP/1.1 400 Bad request" 响应;如果请求为 POSTHEADGET 方法之一,则回显请求内容;否则,向 client_sock 发送 "HTTP/1.1 501 Not Implemented" 响应。最后,释放 request 相关的内存。

最后,修改 echo_server.c 文件的顶部包含 parse.h,以及MakeFile文件确保 parse.hparse.cMakefile 中的依赖关系

三、实验结果和分析

GET

以下分别为 GET 在 server 端(左边)和 client 端(右边)的实验结果。由于server端检查过长,所以只截屏了一部分

image-20240611185439064
image-20240611185513598

下面这个分别为HEAD 在 server 端(左边)和 client 端(右边)的实验结果。

image-20240611185732114

POST

下面这个分别为POST 在 server 端(左边)和 client 端(右边)的实验结果。

image-20240611185825141

以下分别为几个错误类型在 client 端和 server 端的测试结果。

400

image-20240611185933016

501

image-20240611190019458

Error

image-20240611190149560

在自动测试平台上的结果如下图所示:

image-20240611192952651

四.进度总结

本周任务完成表

在“完成”“没完成”列对应打“√”

本周任务要求 完成 没完成 备注
1、阅读HTTP/1.1的标准文档RFC2616
2、搭建编程环境
3、熟悉Socket编程方法;
4、掌握lex和yacc正确解析消息(message)的方法
5.1实现简单的echo web server。Echo GET, HEAD, POST
5.2 响应没有实现的方法
5.3 响应错误的方法
6、功能测试
-------------本文结束感谢您的阅读-------------