この記事について
Rustのnetモジュールを使っていたら出てきたPoll
について自分なりにまとめてみています。
間違っていたら教えてもらえると嬉しいです。
注意
今回調べたPoll
はstd::task::Poll
ではなくmio::Poll
です。
Pollとは?
Poll
allows a program to monitor a large number ofEvented
types, waiting until one or more become "ready" for some class of operations; e.g. reading and writing. An Evented type is considered ready if it is possible to immediately perform a corresponding operation; e.g. read or write.
Evented
型のインスタンスを監視し,中身がReady
になるまで待ちます。
Evented
型のインスタンスがすぐに値を返せる状態になったら,Ready
だとみなします。
エッジトリガとレベルトリガ
Poll
ではイベントが発火するときの方式が2種類あります。
エッジトリガ
割り込み通知などにおいて、信号がある状態から別の状態へ変化した時点でトリガが引かれる方式。
レベルトリガ
信号レベルが変化した瞬間ではなく「信号レベルがある基準より上か下か」という信号の状態に注目してトリガのオン・オフを決める方式。
という感じのようです。(参考)
これが実際にPoll
の中だとどのように使われているのかというと,
With edge-triggered events, operations must be performed on the
Evented
type untilWouldBlock
is returned. In other words, after receiving an event indicating readiness for a certain operation, one should assume thatPoll::poll
may never return another event for the same token and readiness until the operation returnsWouldBlock
.
By contrast, when level-triggered notfications was requested, each call toPoll::poll
will return an event for the socket as long as data remains in the socket buffer. Generally, level-triggered events should be avoided if high performance is a concern.
エッジトリガの方はEvent
型のインスタンスがWouldBlock
を返すまで動き続けます。
レベルトリガの方は,ソケット内にデータが残っている間はEvent
型のインスタンスがイベントを返し続けます。パフォーマンスを求めるならレベルトリガの方は避けるべきみたいです。
サンプルコード
use mio::{Events, Poll, Ready, PollOpt, Token};
use mio::tcp::TcpStream;
use std::net::{TcpListener, SocketAddr};
// Bind a server socket to connect to.
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let server = TcpListener::bind(&addr).unwrap();
// Construct a new `Poll` handle as well as the `Events` we'll store into
let poll = Poll::new().unwrap();
let mut events = Events::with_capacity(1024);
// Connect the stream
let stream = TcpStream::connect(&server.local_addr().unwrap()).unwrap();
// Register the stream with `Poll`
poll.register(&stream, Token(0), Ready::all(), PollOpt::edge()).unwrap();
// Wait for the socket to become ready. This has to happens in a loop to
// handle spurious wakeups.
loop {
poll.poll(&mut events, None).unwrap();
for event in &events {
if event.token() == Token(0) && event.readiness().is_writable() {
// The socket connected (probably, it could still be a spurious
// wakeup)
return;
}
}
}
まず,指定したアドレスにサーバーをバインドします。
次に,Poll
インスタンスをPoll::new()
で作成します。
そしてEvents
型のインスタンスを作成します。これらは両方ともmio
クレートのものです。
次はTcpStream
をバインドしたアドレスに接続します。ここがソケットと呼ばれる部分になります。
そのあとに,poll
インスタンスにstream
とToken(0)
を登録します。
そしてloop
に突入します。
Poll::poll()
にはこのような説明が書いてあります。
Wait for readiness events
Blocks the current thread and waits for readiness events for any of the Evented handles that have been registered with this Poll instance. The function will block until either at least one readiness event has been received or timeout has elapsed. A timeout of None means that poll will block until a readiness event has been received.
なので,poll.poll(&mut events, None).unwrap();
の部分でソケットになにかデータが来るまで待ちます。
受け取ったevents
から要素をforループで取り出し,要素の中身がToken(0)
で,if式の条件を満たしていたらreturn
が実行されます。
まとめ
Poll
でTCPのソケット周りの管理ができるっていう認識でいこうと思います!
参考リンク
https://doc.rust-lang.org/std/task/enum.Poll.html
https://tech-blog.optim.co.jp/entry/2019/07/05/173000
http://mackey-lab.hatenablog.com/entry/2015/04/22/005214