mio是rust的一個低級非阻塞api的網絡庫
文檔地址:https://docs.rs/mio/0.6.20/mio/
官方的介紹:A fast, low-level IO library for Rust focusing on non-blocking APIs, event notification, and other useful utilities for building high performance IO apps.
一個快速、低級別的Rust異步事件驅動的非阻塞API的IO網絡庫,主要用於構建高性能IO應用程序。
底層依賴操作系統的epoll、 kqueue、 IOCP機制,linux系統使用epoll機制,kqueue、 IOCP分別是mac和windows下類似epoll機制,具體可以百度。
epoll是Linux下多路複用IO接口select/poll的增強版本,它能顯著提高程序在大量併發連接中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。
java的nio在linux系統使用epoll機制,mio就類似java的netty框架。
下面是mio官方的例子(重構了下):
庫依賴:
[dependencies]
mio = "0.6"
use mio::net::{TcpListener,TcpStream };
use std::net::SocketAddr;
use mio::{Events, Poll, PollOpt, Ready, Token};
use std::collections::HashMap;
use std::io::{self, Read};
use std::thread;
// 允許服務連接的最大數
const MAX_SOCKETS: usize = 32;
// Pick a token that will not be used by any other socket and use that one
// for the listener. 選一個不能被其它socket連接使用的值做爲listener監聽綁定的token值
const LISTENER: Token = Token(1024);//這裏1024做爲值是,因爲最大連接值爲32,從0開始分配不會使用到1024
fn main() -> io::Result<()> {
// The `Poll` instance
let poll = Poll::new()?;
// Tcp listener
let listener = TcpListener::bind(&"127.0.0.1:3000".parse().unwrap())?;
// Register the listener
poll.register(&listener, LISTENER, Ready::readable(), PollOpt::edge())?;
let addr = listener.local_addr()?;
connect_server(addr);
// Event storage
let mut events = Events::with_capacity(1024);
// Read buffer, this will never actually get filled
let mut buf = [0; 256];
// Used to store the sockets.
let mut sockets = HashMap::new();
let mut next_socket_index = 0;//token index
// The main event loop
loop {
// Wait for events
poll.poll(&mut events, None)?;
for event in &events {
match event.token() {//這裏使用了就緒事件token,從map裏獲取到可用的連接,
//在這個例子裏,只註冊了監聽連接事件和連接到的socket的可讀事件。
LISTENER => {//發現是監聽就緒的token
listen(&listener,&poll,&mut sockets,&mut next_socket_index)?;
if next_socket_index == MAX_SOCKETS {
return Ok(());
}
}
token => {//就緒讀事件
// 一直循環事件
loop { //根據獲取到通知事件的token,從sockets map中取出socket
match sockets.get_mut(&token).unwrap().read(&mut buf) {
Ok(0) => {//讀取字節長度爲0,判斷爲關閉,移出此socket
// Socket is closed, remove it from the map
sockets.remove(&token);
break;
}
Ok(len) => {//讀取成功,把字節數組轉爲字符串打印出來
println!("{}", String::from_utf8(Vec::from(&buf[0..len])).unwrap());
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
// Socket is not ready anymore, stop reading
// socket未就緒,停止讀,跳出循環,否則一直讀
break;
}
e => panic!("err={:?}", e), // Unexpected error
}
}
}
}
}
}
}
fn connect_server(addr :SocketAddr){
for i in 0..(MAX_SOCKETS + 1) {
use std::io::Write;
use std::net::TcpStream;
thread::spawn( move || {
let mut conn = TcpStream::connect(&addr).unwrap();
// conn.set_nonblocking(true); 可以使用非阻塞方式寫入,參考標準庫
conn.write_all(format!("{} hello rust mio\n", i).as_bytes())
.unwrap();
});
}
}
fn listen(
listener: &TcpListener,
poll: &Poll,
sockets: &mut HashMap<Token, TcpStream>, next_socket_index :&mut usize
) -> io::Result<()> {
loop {
match listener.accept() {
Ok((socket, _)) => {
// 創建一個token 下面綁定事件使用
let token = Token(*next_socket_index);
*next_socket_index += 1;// 從0開始分配不會使用到1024,1024的值被listener綁定事件時使用了
// Register the new socket w/ poll
// 把這個新socket的讀事件註冊到poll中
poll.register(&socket, token, Ready::readable(), PollOpt::edge())?;
//保存socket連接到hashmap中,供後續使用
sockets.insert(token, socket);
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
// Socket is not ready anymore, stop accepting
//還沒有socket連接請求,跳出循環,執行其它事件處理
break;
}
e => panic!("err={:?}", e), // Unexpected error
}
}
Ok(())
}
上面的例子主要是服務端監聽3000端口,模擬創建33客戶端去連接服務,併發送一條消息,服務端讀取後打印出來。