Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ch20-01-single-threaded.md #817

Merged
merged 1 commit into from
Sep 16, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions src/ch20-01-single-threaded.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ TCP 是一个底层协议,它描述了信息如何从一个 server 到另一

### 监听 TCP 连接

所以我们的 web server 所需做的第一件事便是能够监听 TCP 连接。标准库提供了 `std::net` 模块处理这些功能。让我们一如既往新建一个项目:
我们的 web server 所需做的第一件事,是监听 TCP 连接。标准库提供了 `std::net` 模块处理这些功能。让我们一如既往新建一个项目:

```console
$ cargo new hello
Created binary (application) `hello` project
$ cd hello
```

并在 `src/main.rs` 输入示例 20-1 中的代码作为开始。这段代码会在地址 `127.0.0.1:7878` 上监听传入的 TCP 流。当获取到传入的流,它会打印出 `Connection established!`:
现在,在 `src/main.rs` 输入示例 20-1 中的代码,作为一个开始。这段代码会在地址 `127.0.0.1:7878` 上监听传入的 TCP 流。当获取到传入的流,它会打印出 `Connection established!`:

<span class="filename">文件名:src/main.rs</span>

Expand All @@ -30,17 +30,17 @@ $ cd hello

<span class="caption">示例 20-1: 监听传入的流并在接收到流时打印信息</span>

`TcpListener` 用于监听 TCP 连接。我们选择监听本地地址 `127.0.0.1:7878`。将这个地址拆开,冒号之前的部分是一个代表本机的 IP 地址(这个地址在每台计算机上都相同,并不特指作者的计算机),而 `7878` 是端口。选择这个端口出于两个原因:通常 HTTP 不接受这个端口的请求所以它不太与你机器上运行的其它 web server 的端口冲突而且 7878 在电话上打出来就是 "rust"(译者注:九宫格键盘上的英文)。
`TcpListener` 用于监听 TCP 连接。我们选择监听本地地址 `127.0.0.1:7878`。将这个地址拆开来看,冒号之前的部分是一个代表本机的 IP 地址(在每台计算机上,这个地址都指本机,并不特指作者的计算机),而 `7878` 是端口。选择这个端口出于两个原因:通常 HTTP 服务器不在这个端口上接受请求,所以它不太可能与你机器上运行的其它 web server 的端口冲突而且 7878 在电话上打出来就是 "rust"(译者注:九宫格键盘上的英文)。

在这个场景中 `bind` 函数类似于 `new` 函数,在这里它返回一个新的 `TcpListener` 实例。这个函数叫做 `bind` 是因为,在网络领域,连接到监听端口被称为 “绑定到一个端口”(“binding to a port”)
在这个场景中 `bind` 函数类似于 `new` 函数,在这里它返回一个新的 `TcpListener` 实例。这个函数叫做 `bind` 是因为,在网络领域,连接到要监听的端口称为“绑定到端口”(“binding to a port”)

`bind` 函数返回 `Result<T, E>`,这表明绑定可能会失败例如,连接 80 端口需要管理员权限(非管理员用户只能监听大于 1023 的端口),所以如果不是管理员尝试连接 80 端口,则会绑定失败。例如如果运行两个此程序的实例这样会有两个程序监听相同的端口,绑定会失败。因为我们是出于学习目的来编写一个基础的 server,将不用关心处理这类错误,使用 `unwrap` 在出现这些情况时直接停止程序。
`bind` 函数返回 `Result<T, E>`,这表明绑定可能会失败例如,监听 80 端口需要管理员权限(非管理员用户只能监听大于 1023 的端口),所以如果尝试监听 80 端口而没有管理员权限,则会绑定失败。再比如,如果我们运行这个程序的两个实例,并因此有两个实例监听同一个端口,那么绑定也将失败。我们是出于学习目的来编写一个基础的服务器,不用关心处理这类错误,而仅仅使用 `unwrap` 在出现这些情况时直接停止程序。

`TcpListener` 的 `incoming` 方法返回一个迭代器,它提供了一系列的流(更准确的说是 `TcpStream` 类型的流)。**流**(*stream*)代表一个客户端和服务端之间打开的连接。**连接**(*connection*)代表客户端连接服务端、服务端生成响应以及服务端关闭连接的全部请求 / 响应过程。为此,我们会从 `TcpStream` 读取客户端发送了什么并接着向流发送响应以向客户端发回数据。总体来说,这个 `for` 循环会依次处理每个连接并产生一系列的流供我们处理。

目前为止,处理流的过程包含 `unwrap` 调用,如果出现任何错误会终止程序,如果没有任何错误,则打印出信息。下一个示例我们将为成功的情况增加更多功能。当客户端连接到服务端时 `incoming` 方法返回错误是可能的,因为我们实际上没有遍历连接,而是遍历 **连接尝试**(*connection attempts*)。连接可能会因为很多原因不能成功,大部分是操作系统相关的。例如,很多系统限制同时打开的连接数;新连接尝试产生错误,直到一些打开的连接关闭为止
目前,处理流的代码中也有一个 `unwrap` 调用,如果 `stream` 出现任何错误会终止程序;如果没有任何错误,则打印出信息。下一个例子中,我们将为成功的情况增加更多功能。当客户端连接到服务端时`incoming` 方法是可能返回错误的,因为我们实际上不是在遍历连接,而是遍历 **连接尝试**(*connection attempts*)。连接的尝试可能会因为多种原因不能成功,大部分是操作系统相关的。例如,很多系统限制同时打开的连接数,超出数量限制的新连接尝试会产生错误,直到一些现有的连接关闭为止

让我们试试这段代码!首先在终端执行 `cargo run`,接着在浏览器中加载 `127.0.0.1:7878`。浏览器会显示出看起来类似于“连接重置”(“Connection reset”)的错误信息,因为 server 目前并没响应任何数据。但是如果我们观察终端,会发现当浏览器连接 server 时会打印出一系列的信息
让我们试试这段代码!首先在终端执行 `cargo run`,接着在浏览器中打开 `127.0.0.1:7878`。浏览器会显示出看起来类似于“连接重置”(“Connection reset”)的错误信息,因为 server 目前并没响应任何数据。如果我们观察终端,会发现当浏览器连接我们的服务端时,会打印出一系列的信息

```text
Running `target/debug/hello`
Expand All @@ -49,15 +49,15 @@ Connection established!
Connection established!
```

有时会看到对于一次浏览器请求会打印出多条信息;这可能是因为浏览器在请求页面的同时还请求了其他资源,比如出现在浏览器 tab 标签中的 *favicon.ico*。
有时,对于一次浏览器请求,可能会打印出多条信息;这可能是因为,浏览器在请求页面的同时,还请求了其他资源,比如出现在浏览器标签页开头的图标(*favicon.ico*

这也可能是因为浏览器尝试多次连接 server,因为 server 没有响应任何数据。当 `stream` 在循环的结尾离开作用域并被丢弃,其连接将被关闭,作为 `drop` 实现的一部分。浏览器有时通过重连来处理关闭的连接,因为这些问题可能是暂时的。现在重要的是我们成功的处理了 TCP 连接!
这也可能是因为浏览器尝试多次连接服务端,因为服务端没有响应任何数据。作为 `drop` 实现的一部分,当 `stream` 在循环的结尾离开作用域并被丢弃,其连接将被关闭。浏览器有时通过重连来处理关闭的连接,因为对于一般网站而言,这些问题可能是暂时的。这些都不重要;现在重要的是,我们成功的处理了 TCP 连接!

记得当运行完特定版本的代码后使用 <span class="keystroke">ctrl-C</span> 来停止程序。并通过执行 `cargo run` 命令在做出最新的代码修改之后重启服务。
记得当运行完特定版本的代码后,使用 <span class="keystroke">ctrl-C</span> 来停止程序。并通过执行 `cargo run` 命令在做出最新的代码修改之后重启服务。

### 读取请求

让我们实现读取来自浏览器请求的功能!为了分离获取连接和接下来对连接的操作的相关内容,我们将开始一个新函数来处理连接。在这个新的 `handle_connection` 函数中,我们从 TCP 流中读取数据并打印出来以便观察浏览器发送过来的数据。将代码修改为如示例 20-2 所示:
让我们实现读取来自浏览器请求的功能!为了分离“获取连接”以及“接下来对连接的操作”,我们将开始写一个新函数来处理连接。在这个新的 `handle_connection` 函数中,我们从 TCP 流中读取数据,并打印出来,以便观察浏览器发送过来的数据。将代码修改为如示例 20-2 所示:

<span class="filename">文件名:src/main.rs</span>

Expand All @@ -67,7 +67,7 @@ Connection established!

<span class="caption">示例 20-2: 读取 `TcpStream` 并打印数据</span>

这里将 `std::io::prelude` 和 `std::io::BufReader` 引入作用域来获取读写流所需的特定 trait。在 `main` 函数的 `for` 循环中,相比获取到连接时打印信息,现在调用新的 `handle_connection` 函数并向其传递 `stream`。
这里将 `std::io::prelude` 和 `std::io::BufReader` 引入作用域,来获取读写流所需的特定 trait。在 `main` 函数的 `for` 循环中,相比获取到连接时打印信息,现在调用新的 `handle_connection` 函数并向其传递 `stream`。

在 `handle_connection` 中,我们新建了一个 `BufReader` 实例来封装一个 `stream` 的可变引用。`BufReader` 增加了缓存来替我们管理 `std::io::Read` trait 方法的调用。

Expand Down
Loading