Hello World
为了开始我们的Tokio之旅,我们将从强制性的“hello world”示例开始。 此服务器将侦听传入连接。 收到连接后,它会向客户端写入“hello world”并关闭连接。
让我们开始吧。
首先,生成一个新的箱子。
$ cargo new --bin hello-world
$ cd hello-world
接下来,添加必要的依赖项:
[dependencies]
tokio = "0.1"
在main.rs
中的引入包和类型:
#![deny(deprecated)]
extern crate tokio;
use tokio::io;
use tokio::net::TcpListener;
use tokio::prelude::*;
fn main() {}
编写服务器
第一步是将TcpListener
绑定到本地端口。我们使用Tokio提供的TcpListener
实现。
#![deny(deprecated)]
extern crate tokio;
#
use tokio::io;
use tokio::net::TcpListener;
use tokio::prelude::*;
fn main() {
let addr = "127.0.0.1:6142".parse().unwrap();
let listener = TcpListener::bind(&addr).unwrap();
// Following snippets come here...
}
接下来,我们定义服务器任务。此异步任务将侦听传入绑定侦听器上的连接并处理每个接受的连接。
#![deny(deprecated)]
extern crate tokio;
#
use tokio::io;
use tokio::net::TcpListener;
use tokio::prelude::*;
fn main() {
let addr = "127.0.0.1:6142".parse().unwrap();
let listener = TcpListener::bind(&addr).unwrap();
let server = listener.incoming().for_each(|socket| {
println!("accepted socket; addr={:?}", socket.peer_addr().unwrap());
// Process socket here.
Ok(())
})
.map_err(|err| {
// All tasks must have an `Error` type of `()`. This forces error
// handling and helps avoid silencing failures.
//
// In our example, we are only going to log the error to STDOUT.
println!("accept error = {:?}", err);
});
}
组合函数用于定义异步任务。调用listener.incoming()
返回已接受连接的[Stream
]。 [Stream
]有点像异步迭代器。
每个组合函数都具有必要状态的所有权回调执行并返回一个新的Future
或Stream
额外的“步骤”序列。
返回的 future
和 Stream
是懒惰的,即在呼叫时不执行任何工作。相反,一旦所有异步步骤都被序列化,那么最终的Future
(代表任务)是在执行者(executor)产生。这是开始运行时候定义的工作。
我们将在以后挖掘Future
和Stream
。
产生任务
执行程序负责调度异步任务,驱动它们完成。有许多执行器实现可供选择,每个都有不同的利弊。在这个例子中,我们将使用Tokio runtime
。
Tokio运行时是异步应用程序的预配置运行时。它包括一个线程池作为默认执行程序。此线程池已调整为用于异步应用程序。
#![deny(deprecated)]
extern crate tokio;
extern crate futures;
#
use tokio::io;
use tokio::net::TcpListener;
use tokio::prelude::*;
use futures::future;
fn main() {
let server = future::ok(());
println!("server running on localhost:6142");
tokio::run(server);
}
tokio :: run
启动运行时,阻塞当前线程直到所有生成的任务都已完成,所有资源(如TCP套接字)都已完成
销毁。使用[tokio :: spawn
] 生成任务必须从内部发生runtime
的上下文。
到目前为止,我们只在执行程序上运行一个任务,所以server
任务是唯一阻止run
返回。
接下来,我们将处理入站套接字。
写入数据
我们的目标是在每个接受的套接字上写上“hello world \ n”。我们会通过定义一个新的异步任务在相同的current_thread
执行者上执行写入和生成该任务。
回到incoming().for_each
块。
#![deny(deprecated)]
extern crate tokio;
#
use tokio::io;
use tokio::net::TcpListener;
use tokio::prelude::*;
fn main() {
let addr = "127.0.0.1:6142".parse().unwrap();
let listener = TcpListener::bind(&addr).unwrap();
let server = listener.incoming().for_each(|socket| {
println!("accepted socket; addr={:?}", socket.peer_addr().unwrap());
let connection = io::write_all(socket, "hello world\n")
.then(|res| {
println!("wrote message; success={:?}", res.is_ok());
Ok(())
});
// Spawn a new task that processes the socket:
tokio::spawn(connection);
Ok(())
})
;
}
我们正在定义另一个异步任务。这项任务将取得所有权socket,在该套接字上写入消息,然后完成。 connection
变量保存最后的任务。同样,还没有完成任何工作。
tokio :: spawn
用于在运行时生成任务。因为server
future在运行时运行,我们可以产生更多的任务。如果从运行时外部调用,tokio :: spawn
将会发生混乱。
[io :: write_all
]函数获取socket
的所有权,返回[Future
]在整个消息写入后完成。 then
用于对写入后运行的步骤进行排序完成。在我们的例子中,我们只是向STDOUT
写一条消息来表示写完了。
请注意,res
是包含原始套接字的Result
。 这允许我们在同一个套接字上对其他读取或写入进行排序。 但是,我们没有其他任何事情可做,所以我们只需删除套接字即可关闭套接字。
你可以在这里找到完整的例子
下一步
本指南的下一页将开始深入研究Tokio运行时模型。