我正在尝试使用 Mio 创建一个简单的事件循环。每个事件都会根据事件的 Token
调用关联的回调。事件循环在与其余代码不同的线程上运行。
事件可以通过 register
函数注册,但是为了注册事件,它必须将它添加到相同的 HashMap
回调(并访问相同的 Mio Poll
结构)事件循环正在迭代。
问题是回调本身可能会注册事件,在这种情况下,不可能采用互斥锁,因为事件循环具有互斥锁。是否可以在调用回调时以某种方式删除 Mutex
函数中的 start()
?虽然,这似乎性能不佳。
在 Rust 中有没有更好的方法来处理这个问题?
use mio::event::Event;
use mio::net::{TcpListener, TcpStream};
use mio::{event, Events, Interest, Poll, Token};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use std::{io, thread};
pub trait HookCb: Send {
fn call(&self, event: &Event);
}
impl<F: Send> HookCb for F
where
F: Fn(&Event),
{
fn call(&self, event: &Event) {
self(event)
}
}
struct EventLoopInner {
handlers: HashMap<Token, Box<dyn HookCb>>,
poll: Poll,
}
pub struct EventLoop {
inner: Mutex<EventLoopInner>,
}
impl EventLoop {
pub fn new() -> io::Result<Self> {
Ok(Self {
inner: Mutex::new(EventLoopInner {
handlers: HashMap::new(),
poll: Poll::new()?,
}),
})
}
pub fn start(&self) -> io::Result<()> {
let mut events = Events::with_capacity(1024);
let inner = &mut *self.inner.lock().unwrap(); // Inner mutex taken
loop {
inner.poll.poll(&mut events, None)?;
for event in events.iter() {
match event.token() {
Token(v) => {
if let Some(cb) = inner.handlers.get(&Token(v)) {
// TODO release the inner mutex before here so that the callback can invoke register
cb.call(event)
}
}
}
}
}
}
pub fn register<S>(
&self,
source: &mut S,
interest: Interest,
cb: impl HookCb + 'static,
) -> io::Result<Token>
where
S: event::Source + std::marker::Send,
{
let mut inner = self.inner.lock().unwrap(); // Cannot get this lock after start() has been invoked
let token = Token(inner.handlers.len());
inner.poll.registry().register(source, token, interest)?;
inner.handlers.insert(token, Box::new(cb));
Ok(token)
}
}
struct ServerConn {
listener: Option<TcpListener>,
connections: HashMap<Token, TcpStream>,
}
struct Server {
eventloop: Arc<EventLoop>,
thread: Option<JoinHandle<()>>,
conn: Arc<Mutex<ServerConn>>,
}
impl Server {
pub fn new() -> Self {
Self {
eventloop: Arc::new(EventLoop::new().unwrap()),
thread: None,
conn: Arc::new(Mutex::new(ServerConn {
listener: None,
connections: HashMap::new(),
})),
}
}
pub fn listen(&mut self, addr: &str) {
{
let mut conn = self.conn.lock().unwrap();
conn.listener = Some(TcpListener::bind(addr.parse().unwrap()).unwrap());
let cb_conn = Arc::clone(&self.conn);
let cb_eventloop = Arc::clone(&self.eventloop);
self.eventloop
.register(
conn.listener.as_mut().unwrap(),
Interest::READABLE,
move |e: &Event| {
Self::accept_cb(e, &cb_conn, &cb_eventloop);
},
)
.unwrap();
} // Unlock conn
let t_eventloop = Arc::clone(&self.eventloop);
self.thread = Some(thread::spawn(move || {
t_eventloop.start().unwrap();
}));
self.thread.take().unwrap().join().unwrap(); // Temp fix to block main thread so application does not exit
}
fn accept_cb(_e: &Event, conn: &Arc<Mutex<ServerConn>>, evloop: &Arc<EventLoop>) {
let mut conn_lock = conn.lock().unwrap();
loop {
let (mut stream, addr) = match conn_lock.listener.as_ref().unwrap().accept() {
Ok((stream, addr)) => (stream, addr),
Err(_) => return,
};
println!("Accepted connection from: {}", addr);
// TODO can this clone be avoided?
let cb_conn = Arc::clone(conn);
let cb_evloop = Arc::clone(evloop);
let token = evloop
.register(
&mut stream,
Interest::READABLE.add(Interest::WRITABLE),
move |e: &Event| {
Self::conn_cb(e, &cb_conn, &cb_evloop);
},
)
.unwrap();
conn_lock.connections.insert(token, stream);
}
}
pub fn conn_cb(e: &Event, conn: &Arc<Mutex<ServerConn>>, _evloop: &Arc<EventLoop>) {
let conn_lock = conn.lock().unwrap();
let mut _connection = conn_lock.connections.get(&e.token()).unwrap();
if e.is_writable() {
// TODO write logic -- connection.write(b"Hello World!\n").unwrap();
// TODO evloop.reregister(&mut connection, event.token(), Interest::READABLE)?
}
if e.is_readable() {
// TODO read logic -- connection.read(&mut received_data);
}
}
}
fn main() {
let mut s = Server::new();
s.listen("127.0.0.1:8000");
}