Hello World

为了开始我们的Tokio之旅,我们将从强制性的“hello world”示例开始。 此服务器将侦听传入连接。 收到连接后,它会向客户端写入“hello world”并关闭连接。

让我们开始吧。

首先,生成一个新的箱子。

  1. $ cargo new --bin hello-world
  2. $ cd hello-world

接下来,添加必要的依赖项:

  1. [dependencies]
  2. tokio = "0.1"

main.rs中的引入包和类型:

  1. #![deny(deprecated)]
  2. extern crate tokio;
  3. use tokio::io;
  4. use tokio::net::TcpListener;
  5. use tokio::prelude::*;
  6. fn main() {}

编写服务器

第一步是将TcpListener绑定到本地端口。我们使用Tokio提供的TcpListener实现。

  1. #![deny(deprecated)]
  2. extern crate tokio;
  3. #
  4. use tokio::io;
  5. use tokio::net::TcpListener;
  6. use tokio::prelude::*;
  7. fn main() {
  8. let addr = "127.0.0.1:6142".parse().unwrap();
  9. let listener = TcpListener::bind(&addr).unwrap();
  10. // Following snippets come here...
  11. }

接下来,我们定义服务器任务。此异步任务将侦听传入绑定侦听器上的连接并处理每个接受的连接。

  1. #![deny(deprecated)]
  2. extern crate tokio;
  3. #
  4. use tokio::io;
  5. use tokio::net::TcpListener;
  6. use tokio::prelude::*;
  7. fn main() {
  8. let addr = "127.0.0.1:6142".parse().unwrap();
  9. let listener = TcpListener::bind(&addr).unwrap();
  10. let server = listener.incoming().for_each(|socket| {
  11. println!("accepted socket; addr={:?}", socket.peer_addr().unwrap());
  12. // Process socket here.
  13. Ok(())
  14. })
  15. .map_err(|err| {
  16. // All tasks must have an `Error` type of `()`. This forces error
  17. // handling and helps avoid silencing failures.
  18. //
  19. // In our example, we are only going to log the error to STDOUT.
  20. println!("accept error = {:?}", err);
  21. });
  22. }

组合函数用于定义异步任务。调用listener.incoming()返回已接受连接的[Stream]。 [Stream]有点像异步迭代器。

每个组合函数都具有必要状态的所有权回调执行并返回一个新的FutureStream额外的“步骤”序列。

返回的 futureStream是懒惰的,即在呼叫时不执行任何工作。相反,一旦所有异步步骤都被序列化,那么最终的Future(代表任务)是在执行者(executor)产生。这是开始运行时候定义的工作。

我们将在以后挖掘FutureStream

产生任务

执行程序负责调度异步任务,驱动它们完成。有许多执行器实现可供选择,每个都有不同的利弊。在这个例子中,我们将使用Tokio runtime

Tokio运行时是异步应用程序的预配置运行时。它包括一个线程池作为默认执行程序。此线程池已调整为用于异步应用程序。

  1. #![deny(deprecated)]
  2. extern crate tokio;
  3. extern crate futures;
  4. #
  5. use tokio::io;
  6. use tokio::net::TcpListener;
  7. use tokio::prelude::*;
  8. use futures::future;
  9. fn main() {
  10. let server = future::ok(());
  11. println!("server running on localhost:6142");
  12. tokio::run(server);
  13. }

tokio :: run启动运行时,阻塞当前线程直到所有生成的任务都已完成,所有资源(如TCP套接字)都已完成 销毁。使用[tokio :: spawn] 生成任务必须从内部发生runtime的上下文。

到目前为止,我们只在执行程序上运行一个任务,所以server任务是唯一阻止run返回。

接下来,我们将处理入站套接字。

写入数据

我们的目标是在每个接受的套接字上写上“hello world \ n”。我们会通过定义一个新的异步任务在相同的current_thread执行者上执行写入和生成该任务。

回到incoming().for_each块。

  1. #![deny(deprecated)]
  2. extern crate tokio;
  3. #
  4. use tokio::io;
  5. use tokio::net::TcpListener;
  6. use tokio::prelude::*;
  7. fn main() {
  8. let addr = "127.0.0.1:6142".parse().unwrap();
  9. let listener = TcpListener::bind(&addr).unwrap();
  10. let server = listener.incoming().for_each(|socket| {
  11. println!("accepted socket; addr={:?}", socket.peer_addr().unwrap());
  12. let connection = io::write_all(socket, "hello world\n")
  13. .then(|res| {
  14. println!("wrote message; success={:?}", res.is_ok());
  15. Ok(())
  16. });
  17. // Spawn a new task that processes the socket:
  18. tokio::spawn(connection);
  19. Ok(())
  20. })
  21. ;
  22. }

我们正在定义另一个异步任务。这项任务将取得所有权socket,在该套接字上写入消息,然后完成。 connection变量保存最后的任务。同样,还没有完成任何工作。

tokio :: spawn用于在运行时生成任务。因为server future在运行时运行,我们可以产生更多的任务。如果从运行时外部调用,tokio :: spawn将会发生混乱。

[io :: write_all]函数获取socket的所有权,返回[Future]在整个消息写入后完成。 then用于对写入后运行的步骤进行排序完成。在我们的例子中,我们只是向STDOUT写一条消息来表示写完了。

请注意,res是包含原始套接字的Result。 这允许我们在同一个套接字上对其他读取或写入进行排序。 但是,我们没有其他任何事情可做,所以我们只需删除套接字即可关闭套接字。

你可以在这里找到完整的例子

下一步

本指南的下一页将开始深入研究Tokio运行时模型。