辣鸡知乎连目录都不能打。推荐阅读原文rust asynchronous io - 李晨曦的博客 | Hexi Bloghexilee.me
引言
2018 年接近尾声,rust 团队勉强立住了异步 IO 的 flag,async 成为了关键字,Pin, Future, Poll 和 await! 也进入了标准库。不过一直以来实际项目中用不到这套东西,所以也没有主动去了解过。
最近心血来潮想用 rust 写点东西,但并找不到比较能看的文档(可能是因为 rust 发展太快了,很多都过时了),最后参考这篇文章和 "new tokio"( romio ) 写了几个 demo,并基于 mio 在 coroutine 中实现了简陋的异步 IO。
最终效果如下:
// examples/async-echo.rs#![feature(async_await)]#![feature(await_macro)]#[macro_use]externcratelog;useasyncio::executor::{block_on,spawn,TcpListener};usefailure::Error;fn main()-> Result{env_logger::init();block_on(async{letmutlistener=TcpListener::bind(&"127.0.0.1:7878".parse().unwrap()).expect("TcpListener bind fail");info!("Listening on 127.0.0.1:7878");whileletOk((mutstream,addr))=await!(listener.accept()){info!("connection from {}",addr);spawn(asyncmove{letclient_hello=await!(stream.read()).expect("read from stream fail");letread_length=client_hello.len();letwrite_length=await!(stream.write(client_hello)).expect("write to stream fail");assert_eq!(read_length,write_length);stream.close();},).expect("spawn stream fail");}},)}
写这篇文章的主要目的是梳理和总结,同时也希望能给对这方面有兴趣的 Rustacean 作为参考。本文代码以易于理解为主要编码原则,某些地方并没有太考虑性能,还请见谅;但如果文章和代码中有明显错误,欢迎指正。
本文代码仓库在 Github (部分代码较长,建议 clone 下来用编辑器看),所有 examples 在 nightly-x86_64-apple-darwin 2018 Edition 上均能正常运行。运行 example/async-echo 时设置 RUST_LOG 为 info 可以在 terminal 看到基本的运行信息,debug 则可见事件循环中的事件触发顺序。
异步 IO 的基石 - mio
mio 是一个极简的底层异步 IO 库,如今 rust 生态中几乎所有的异步 IO 程序都基于它。
随着 channel, timer 等 sub module 在 0.6.5 版本被标为 deprecated,如今的 mio 提供的唯二两个核心功能分别是:对操作系统异步网络 IO 的封装
用户自定义事件队列
第一个核心功能对应到不同操作系统分别是Linux(Android) => epoll
Windows => iocp
MacOS(iOS), FreeBSD => kqueue
Fuchsia =>
mio 把这些不同平台上的 API 封装出了一套 epoll like 的异步网络 API,支持 udp 和 tcp。除此之外还封装了一些不同平台的拓展 API,比如 uds,本文不对这些 API 做介绍。
异步网络 IO
下面是一个 tcp 的 demo
// examples/tcp.rsusemio::*;usemio::net::{TcpListener,TcpStream};usestd::io::{Read,Write,self};usefailure::Error;usestd::time::{Duration,Instant};constSERVER_ACCEPT: Token=Token(0);constSERVER: Token=Token(1);constCLIENT: Token=Token(2);constSERVER_HELLO: &[u8]=b"PING";constCLIENT_HELLO: &[u8]=b"PONG";fn main()-> Result{letaddr="127.0.0.1:13265".parse()?;// Setup the server socketletserver=TcpListener::bind(&addr)?;// Create a poll instanceletpoll=Poll::new()?;// Start listening for incoming connectionspoll.register(&server,SERVER_ACCEPT,Ready::readable(),PollOpt::edge())?;// Setup the client socketletmutclient=TcpStream::connect(&addr)?;letmutserver_handler=None;// Register the clientpoll.register(&client,CLIENT,Ready::readable()|Ready::writable(),PollOpt::edge())?;// Create storage for eventsletmutevents=Events::with_capacity(1024);letstart=Instant::now();lettimeout=Duration::from_millis(10);'top: loop{poll.poll(&mutevents,None)?;foreventinevents.iter(){ifstart.elapsed()>=timeout{break'top}matchevent.token(){SERVER_ACCEPT=>{let(handler,addr)=server.accept()?;println!("accept from addr: {}",&addr);poll.register(&handler,SERVER,Ready::readable()|Ready::writable(),PollOpt::edge())?;server_handler=Some(handler);}SERVER=>{ifevent.readiness().is_writable(){ifletSome(refmuthandler)=&mutserver_handler{matchhandler.write(SERVER_HELLO){Ok(_)=>{println!("server wrote");}Err(referr)iferr.kind()==io::ErrorKind::WouldBlock=>continue,err=>{err?;}}}}ifevent.readiness().is_readable(){letmuthello=[0;4];ifletSome(refmuthandler)=&mutserver_handler{matchhandler.read_exact(&muthello){Ok(_)=>{assert_eq!(CLIENT_HELLO,&hello);println!("server received");}Err(referr)iferr.kind()==io::ErrorKind::WouldBlock=>continue,err=>{err?;}}}}}CLIENT=>{ifevent.readiness().is_writable(){matchclient.write(CLIENT_HELLO){Ok(_)=>{println!("client wrote");}Err(referr)iferr.kind()==io::ErrorKind::WouldBlock=>continue,err=>{err?;}}}ifevent.readiness().is_readable(){letmuthello=[0;4];matchclient.read_exact(&muthello){Ok(_)=>{assert_eq!(SERVER_HELLO,&hello);println!("client received");}Err(referr)iferr.kind()==io::ErrorKind::WouldBlock=>continue,err=>{err?;}}}}_=>unreachable!(),}}};Ok(())}
这个 demo 稍微有点长,接下来我们把它一步步分解。
直接看主循环
fn main(){// ...loop{poll.poll(&mutevents,None).unwrap();// ...}}
每次循环都得执行 poll.poll,第一个参数是用来存 events 的 Events, 容量是 1024;
letmutevents=Events::with_capacity(1024);
第二个参数是 timeout,即一个 Option,超时会直接返回。返回类型是 io::Result。其中的 usize 代表 events 的数量,这个返回值是 deprecated 并且会在之后的版本移除,仅供参考
这里我们设置了 timeout = None,所以当这个函数返回时,必然是某些事件被触发了。让我们遍历 events:
matchevent.token(){SERVER_ACCEPT=>{let(handler,addr)=server.accept()?;println!("accept from addr: {}",&addr);poll.register(&handler,SERVER,Ready::readable()|Ready::writable(),PollOpt::edge())?;server_handler=Some(handler);}SERVER=>{ifevent.readiness().is_writable(){ifletSome(refmuthandler)=&mutserver_handler{matchhandler.write(SERVER_HELLO){Ok(_)=>{println!("server wrote");}Err(referr)iferr.kind()==io::ErrorKind::WouldBlock=>continue,err=>{err?;}}}}ifevent.readiness().is_readable(){letmuthello=[0;4];ifletSome(refmuthandler)=&mutserver_handler{matchhandler.read_exact(&muthello){Ok(_)=>{assert_eq!(CLIENT_HELLO,&hello);println!("server received");}Err(referr)iferr.kind()==io::ErrorKind::WouldBlock=>continue,err=>{err?;}}}}}CLIENT=>{ifevent.readiness().is_writable(){matc