Skip to content

Commit

Permalink
add examples to test MIO for HermitOS
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stlankes committed Feb 13, 2024
1 parent d72f31f commit 43062d0
Show file tree
Hide file tree
Showing 5 changed files with 354 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ members = [
"examples/httpd",
"examples/testtcp",
"examples/testudp",
"examples/miotcp",
"examples/mioudp",
"examples/tokio",
"examples/webserver",
"hermit",
Expand Down
30 changes: 30 additions & 0 deletions examples/miotcp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "miotcp"
authors = ["Stefan Lankes <[email protected]>"]
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"]
198 changes: 198 additions & 0 deletions examples/miotcp/src/main.rs
Original file line number Diff line number Diff line change
@@ -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<bool> {
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")
}
30 changes: 30 additions & 0 deletions examples/mioudp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "mioudp"
authors = ["Stefan Lankes <[email protected]>"]
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"]
94 changes: 94 additions & 0 deletions examples/mioudp/src/main.rs
Original file line number Diff line number Diff line change
@@ -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")
}

0 comments on commit 43062d0

Please sign in to comment.