mio::poll文档 —— 翻译和注解

翻译自mio文档: Poll

Struct mio::Poll

1
pub struct Poll { /* fields omitted */ }

Polls for readiness events on all registered values.

Poll allows a program to monitor a large number of Evented 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.

To use Poll, an Evented type must first be registered with the Poll instance using the register method, supplying readiness interest. The readiness interest tells Poll which specific operations on the handle to monitor for readiness. A Token is also passed to the register function. When Poll returns a readiness event, it will include this token. This associates the event with the Evented handle that generated the event.

Poll用于从所有注册的value里poll处于就绪状态的事件。这个跟Java里NIO里的概念可以类比下:

  • Selector <-> Poll。可以把Evented注册到Poll上,使用它来监听Evented有关的IO事件。是实现异步IO的关键组件。
  • Evented <-> SelectionKey。可以通过SelectionKey获取底层的channel进行读写。Evented可以直接进行读写。
  • Token <-> SelectionKey里attach的对象。用于将事件跟Evented关联起来。

例子


一个基础的例子,创建一个TcpStream连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use mio::{Events, Poll, Ready, PollOpt, Token};
use mio::net::TcpStream;

use std::net::{TcpListener, SocketAddr};

// 创建一个server socket, 绑定到指定地址
let addr: SocketAddr = "127.0.0.1:0".parse()?;
let server = TcpListener::bind(&addr)?;

// Construct a new `Poll` handle as well as the `Events` we'll store into
let poll = Poll::new()?;
let mut events = Events::with_capacity(1024);


// Connect the stream
let stream = TcpStream::connect(&server.local_addr()?)?;

// 把stream注册到`Poll`
poll.register(&stream, Token(0), Ready::readable() | Ready::writable(), PollOpt::edge())?;

// Wait for the socket to become ready. This has to happens in a loop to
// handle spurious wakeups.
//等待socket可用。需要在一个处理虚假的wakeup的循环里做这件事
loop {
poll.poll(&mut events, None)?;

for event in &events {
if event.token() == Token(0) && event.readiness().is_writable() {
// The socket connected (probably, it could still be a spurious
// wakeup)
// socket连接上了(只是可能连接上了,仍然可能是一个假的wakeup)
return Ok(());
}
}
}

Events::with_capacity

在poll的源码的注释里写到

1
2
3
4
/// The supplied `events` will be cleared and newly received readiness events
/// will be pushed onto the end. At most `events.capacity()` events will be
/// returned. If there are further pending readiness events, they will be
/// returned on the next call to `poll`.

Edge-triggered and level-triggered


问题在于,对于已经处于就绪状态的key,如果我们在poll返回的集合里没有对它进行操作, 例如没有读写,那么
下一次poll的时候,它还会在集合里吗?

Java NIO 的作法

Java NIO的Selector维持了三个集合:

  • key set。包含了所有注册到selector的channel对应的key
  • selected-key set。在这个集合中的key对应的channel处于readiness状态。
  • cancelled-key set。这些key已经被cancel,但是相关的channel还没有被deregistered。

Keys are added to the selected-key set by selection operations. A key may be removed directly from the selected-key set by invoking the set’s remove method or by invoking the remove method of an iterator obtained from the set. Keys are never removed from the selected-key set in any other way; they are not, in particular, removed as a side effect of selection operations. Keys may not be added directly to the selected-key set.

对Java的NIO框架,所有处于就绪状态的key会一直在Selector#selectedKey()返回的集合中,除非你主动移除它。

Rust mio的作法

Rust对上边这个问题的处理,给用户提供了两种选择:edge-triggered 以及 level-triggered.
这个跟电路的相关知识类似。
下边是对相关文档的解要翻译:

一个Evented在注册的时候可以选择请求的是edge-triggered事件,还是level-triggered的事件。
试想有下面的场景发生:

  1. 通过Poll注册了一个TcpStream
  2. socket接收到了2kb数据
  3. Poll::poll的调用返回了token以及绑定在这个token上的socket,指示说这个socket处于读就绪状态。
  4. 从socket里读取了1kb数据
  5. 再次调用Poll::poll

如果在注册这个socket时要求的是edge-triggered event,那么当在第5步调用Poll::poll时可能会hang住,即使现在在socket的read buffer里有1kb数据。原因是edge-triggered模式只有当被监听的Evented有事件发生时才会投递事件。

当使用edger-triggered events时,必须对Evented进行处理,直接它返回WouldBlock。按句话说,在收到了指标说就绪状态的消息以后,你必须假设Poll::poll以后不会对这个token再返回消息,直到你对它的操作返回WouldBlock

作为对比的是,当要求的是level-triggered通知时,只要相关的socket buffer有数据就会返回event。通常来说,当需要高性能时,就要避免使用level-triggered events.

Since even with edge-triggered events, multiple events can be generated upon receipt of multiple chunks of data, the caller has the option to set the oneshot flag. This tells Poll to disable the associated Evented after the event is returned from Poll::poll. The subsequent calls to Poll::poll will no longer include events for Evented handles that are disabled even if the readiness state changes. The handle can be re-enabled by calling reregister. When handles are disabled, internal resources used to monitor the handle are maintained until the handle is dropped or deregistered. This makes re-registering the handle a fast operation.

For example, in the following scenario:

  1. A TcpStream is registered with Poll.
  2. The socket receives 2kb of data.
  3. A call to Poll::poll returns the token associated with the socket indicating readable readiness.
  4. 2kb is read from the socket.
  5. Another call to read is issued and WouldBlock is returned
  6. The socket receives another 2kb of data.
  7. Another call to Poll::poll is made.
    Assuming the socket was registered with Poll with the edge and oneshot options, then the call to Poll::poll in step 7 would block. This is because, oneshot tells Poll to disable events for the socket after returning an event.

In order to receive the event for the data received in step 6, the socket would need to be reregistered using reregister.

这一段是说在注册到Poll的时候有个oneshot标识,当它被set以后,当相关的Evented的第一个event被返回以后,这个Evented的handle就被disable了,直到你调用reregister。由于在Evented的handle被disable以后,监控它所使用的内部资源没有被释放,所以这个re-register操作是一个很快的操作。

这个特性是Java所没有的。

可移植性


只要按照下面的规范,Poll就提供了对所支持的所有平台都可用的接口。

Spurious Events

即使相关联的Eventedhandle没有真的就绪,Poll::poll也可能返回就绪事件。

如果操作失败,返回WouldBlock,那么调用者不应该把它当作一个错误,因该等待下一个就绪事件到来。

Draining readiness

当使用edge-triggered模式时,当收到一个就绪事件时,应该一直执行操作,直到WouldBlock被返回。

Readiness operations

只有readablewritable才是所有平台都支持的readiness operation。
所以即使注册了对huperror的关注,当收到相关消息时,也只在通过实际的读写才能知道直正的情况。

Registering handles

除非另外说明,应该假设实现了Evented的类型只有在被注册到Poll以后才可能就绪。