From 43062d0b821e66864eb29165eaa827f95d9f7941 Mon Sep 17 00:00:00 2001 From: Stefan Lankes Date: Sun, 11 Feb 2024 22:14:59 +0100 Subject: [PATCH] add examples to test MIO for HermitOS Mio is a fast, low-level I/O library for Rust focusing on non-blocking APIs and event notification for building high performance I/O. --- Cargo.toml | 2 + examples/miotcp/Cargo.toml | 30 ++++++ examples/miotcp/src/main.rs | 198 ++++++++++++++++++++++++++++++++++++ examples/mioudp/Cargo.toml | 30 ++++++ examples/mioudp/src/main.rs | 94 +++++++++++++++++ 5 files changed, 354 insertions(+) create mode 100644 examples/miotcp/Cargo.toml create mode 100644 examples/miotcp/src/main.rs create mode 100644 examples/mioudp/Cargo.toml create mode 100644 examples/mioudp/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index ac6d0e334..797161632 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ members = [ "examples/httpd", "examples/testtcp", "examples/testudp", + "examples/miotcp", + "examples/mioudp", "examples/tokio", "examples/webserver", "hermit", diff --git a/examples/miotcp/Cargo.toml b/examples/miotcp/Cargo.toml new file mode 100644 index 000000000..4d7165720 --- /dev/null +++ b/examples/miotcp/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "miotcp" +authors = ["Stefan Lankes "] +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +mio = { git = "https://github.com/hermit-os/mio.git", branch = "hermit", features = ["net", "os-poll"] } +env_logger = { version = "0.9.3", default-features = false } +log = { version = "0.4.8" } + +[target.'cfg(target_os = "hermit")'.dependencies.hermit] +path = "../../hermit" +default-features = false + +[features] +default = ["pci", "pci-ids", "acpi", "tcp"] +vga = ["hermit/vga"] +dhcpv4 = ["hermit/dhcpv4"] +pci = ["hermit/pci"] +pci-ids = ["hermit/pci-ids"] +acpi = ["hermit/acpi"] +fsgsbase = ["hermit/fsgsbase"] +smp = ["hermit/smp"] +tcp = ["hermit/tcp"] +udp = ["hermit/udp"] +instrument = ["hermit/instrument"] +trace = ["hermit/trace"] +rtl8139 = ["hermit/rtl8139"] diff --git a/examples/miotcp/src/main.rs b/examples/miotcp/src/main.rs new file mode 100644 index 000000000..6862b32ce --- /dev/null +++ b/examples/miotcp/src/main.rs @@ -0,0 +1,198 @@ +#[cfg(target_os = "hermit")] +use hermit as _; + +// This example is derived from +// https://github.com/tokio-rs/mio/blob/master/examples/tcp_server.rs + +use mio::event::Event; +use mio::net::{TcpListener, TcpStream}; +use mio::{Events, Interest, Poll, Registry, Token}; +use std::collections::HashMap; +use std::io::{self, Read, Write}; +use std::str::from_utf8; + +// Setup some tokens to allow us to identify which event is for which socket. +const SERVER: Token = Token(0); + +// Some data we'll send over the connection. +const DATA: &[u8] = b"Hello world!\n"; + +#[cfg(not(target_os = "wasi"))] +fn main() -> io::Result<()> { + env_logger::init(); + + // Create a poll instance. + let mut poll = Poll::new()?; + // Create storage for events. + let mut events = Events::with_capacity(128); + + // Setup the TCP server socket. + let addr = "0.0.0.0:9975".parse().unwrap(); + let mut server = TcpListener::bind(addr)?; + + // Register the server with poll we can receive events for it. + poll.registry() + .register(&mut server, SERVER, Interest::READABLE)?; + + // Map of `Token` -> `TcpStream`. + let mut connections = HashMap::new(); + // Unique token for each incoming connection. + let mut unique_token = Token(SERVER.0 + 1); + + println!("You can connect to the server using `nc`:"); + println!(" $ nc 127.0.0.1 9975"); + println!("You'll see our welcome message and anything you type will be printed here."); + + loop { + if let Err(err) = poll.poll(&mut events, None) { + if interrupted(&err) { + continue; + } + return Err(err); + } + + for event in events.iter() { + match event.token() { + SERVER => loop { + // Received an event for the TCP server socket, which + // indicates we can accept an connection. + let (mut connection, address) = match server.accept() { + Ok((connection, address)) => (connection, address), + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + // If we get a `WouldBlock` error we know our + // listener has no more incoming connections queued, + // so we can return to polling and wait for some + // more. + break; + } + Err(e) => { + // If it was any other kind of error, something went + // wrong and we terminate with an error. + return Err(e); + } + }; + + println!("Accepted connection from: {}", address); + + let token = next(&mut unique_token); + poll.registry().register( + &mut connection, + token, + Interest::READABLE.add(Interest::WRITABLE), + )?; + + connections.insert(token, connection); + }, + token => { + // Maybe received an event for a TCP connection. + let done = if let Some(connection) = connections.get_mut(&token) { + handle_connection_event(poll.registry(), connection, event)? + } else { + // Sporadic events happen, we can safely ignore them. + false + }; + if done { + if let Some(mut connection) = connections.remove(&token) { + poll.registry().deregister(&mut connection)?; + } + } + } + } + } + } +} + +fn next(current: &mut Token) -> Token { + let next = current.0; + current.0 += 1; + Token(next) +} + +/// Returns `true` if the connection is done. +fn handle_connection_event( + registry: &Registry, + connection: &mut TcpStream, + event: &Event, +) -> io::Result { + if event.is_writable() { + // We can (maybe) write to the connection. + match connection.write(DATA) { + // We want to write the entire `DATA` buffer in a single go. If we + // write less we'll return a short write error (same as + // `io::Write::write_all` does). + Ok(n) if n < DATA.len() => return Err(io::ErrorKind::WriteZero.into()), + Ok(_) => { + // After we've written something we'll reregister the connection + // to only respond to readable events. + registry.reregister(connection, event.token(), Interest::READABLE)? + } + // Would block "errors" are the OS's way of saying that the + // connection is not actually ready to perform this I/O operation. + Err(ref err) if would_block(err) => {} + // Got interrupted (how rude!), we'll try again. + Err(ref err) if interrupted(err) => { + return handle_connection_event(registry, connection, event) + } + // Other errors we'll consider fatal. + Err(err) => return Err(err), + } + } + + if event.is_readable() { + let mut connection_closed = false; + let mut received_data = vec![0; 4096]; + let mut bytes_read = 0; + // We can (maybe) read from the connection. + loop { + match connection.read(&mut received_data[bytes_read..]) { + Ok(0) => { + // Reading 0 bytes means the other side has closed the + // connection or is done writing, then so are we. + connection_closed = true; + break; + } + Ok(n) => { + bytes_read += n; + if bytes_read == received_data.len() { + received_data.resize(received_data.len() + 1024, 0); + } + } + // Would block "errors" are the OS's way of saying that the + // connection is not actually ready to perform this I/O operation. + Err(ref err) if would_block(err) => break, + Err(ref err) if interrupted(err) => continue, + // Other errors we'll consider fatal. + Err(err) => return Err(err), + } + } + + if bytes_read != 0 { + let received_data = &received_data[..bytes_read]; + if let Ok(str_buf) = from_utf8(received_data) { + println!("Received data: {}", str_buf.trim_end()); + } else { + println!("Received (none UTF-8) data: {:?}", received_data); + } + } + + if connection_closed { + println!("Connection closed"); + return Ok(true); + } + } + + Ok(false) +} + +fn would_block(err: &io::Error) -> bool { + err.kind() == io::ErrorKind::WouldBlock +} + +fn interrupted(err: &io::Error) -> bool { + err.kind() == io::ErrorKind::Interrupted +} + +#[cfg(target_os = "wasi")] +fn main() { + panic!("can't bind to an address with wasi") +} diff --git a/examples/mioudp/Cargo.toml b/examples/mioudp/Cargo.toml new file mode 100644 index 000000000..7b8c7b741 --- /dev/null +++ b/examples/mioudp/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "mioudp" +authors = ["Stefan Lankes "] +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +mio = { git = "https://github.com/hermit-os/mio.git", branch = "hermit", features = ["net", "os-poll"] } +env_logger = { version = "0.9.3", default-features = false } +log = { version = "0.4.8" } + +[target.'cfg(target_os = "hermit")'.dependencies.hermit] +path = "../../hermit" +default-features = false + +[features] +default = ["pci", "pci-ids", "acpi", "udp"] +vga = ["hermit/vga"] +dhcpv4 = ["hermit/dhcpv4"] +pci = ["hermit/pci"] +pci-ids = ["hermit/pci-ids"] +acpi = ["hermit/acpi"] +fsgsbase = ["hermit/fsgsbase"] +smp = ["hermit/smp"] +tcp = ["hermit/tcp"] +udp = ["hermit/udp"] +instrument = ["hermit/instrument"] +trace = ["hermit/trace"] +rtl8139 = ["hermit/rtl8139"] diff --git a/examples/mioudp/src/main.rs b/examples/mioudp/src/main.rs new file mode 100644 index 000000000..d0cce7143 --- /dev/null +++ b/examples/mioudp/src/main.rs @@ -0,0 +1,94 @@ +#[cfg(target_os = "hermit")] +use hermit as _; + +// This example is derived from +// https://github.com/tokio-rs/mio/blob/master/examples/tcp_server.rs + +use log::warn; +use mio::{Events, Interest, Poll, Token}; +use std::io; + +// A token to allow us to identify which event is for the `UdpSocket`. +const UDP_SOCKET: Token = Token(0); + +#[cfg(not(target_os = "wasi"))] +fn main() -> io::Result<()> { + use mio::net::UdpSocket; + + env_logger::init(); + + // Create a poll instance. + let mut poll = Poll::new()?; + // Create storage for events. Since we will only register a single socket, a + // capacity of 1 will do. + let mut events = Events::with_capacity(1); + + // Setup the UDP socket. + let addr = "0.0.0.0:9975".parse().unwrap(); + + let mut socket = UdpSocket::bind(addr)?; + + // Register our socket with the token defined above and an interest in being + // `READABLE`. + poll.registry() + .register(&mut socket, UDP_SOCKET, Interest::READABLE)?; + + println!("You can connect to the server using `nc`:"); + println!(" $ nc -u 127.0.0.1 9975"); + println!("Anything you type will be echoed back to you."); + + // Initialize a buffer for the UDP packet. We use the maximum size of a UDP + // packet, which is the maximum value of 16 a bit integer. + let mut buf = [0; 1 << 16]; + + // Our event loop. + loop { + // Poll to check if we have events waiting for us. + if let Err(err) = poll.poll(&mut events, None) { + if err.kind() == io::ErrorKind::Interrupted { + continue; + } + return Err(err); + } + + // Process each event. + for event in events.iter() { + // Validate the token we registered our socket with, + // in this example it will only ever be one but we + // make sure it's valid none the less. + match event.token() { + UDP_SOCKET => loop { + // In this loop we receive all packets queued for the socket. + match socket.recv_from(&mut buf) { + Ok((packet_size, source_address)) => { + // Echo the data. + socket.send_to(&buf[..packet_size], source_address)?; + } + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + // If we get a `WouldBlock` error we know our socket + // has no more packets queued, so we can return to + // polling and wait for some more. + break; + } + Err(e) => { + // If it was any other kind of error, something went + // wrong and we terminate with an error. + return Err(e); + } + } + }, + _ => { + // This should never happen as we only registered our + // `UdpSocket` using the `UDP_SOCKET` token, but if it ever + // does we'll log it. + warn!("Got event for unexpected token: {:?}", event); + } + } + } + } +} + +#[cfg(target_os = "wasi")] +fn main() { + panic!("can't bind to an address with wasi") +}