corutine rust_rust 异步 IO:从 mio 到 coroutine

本文介绍了Rust异步IO的基础,包括mio库的使用,展示了如何在coroutine中实现异步TCP通信。通过示例代码详细解析了事件循环的工作原理和错误处理策略,并探讨了自引用、Pin和Generator在异步编程中的挑战及解决方法。文章结尾提到了未来异步API的不足和改进方向。
摘要由CSDN通过智能技术生成

辣鸡知乎连目录都不能打。推荐阅读原文rust asynchronous io - 李晨曦的博客 | Hexi Blog​hexilee.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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值