A higher-level event loop built on top of mio
. mai
manages buffers and streams so you can focus on sending and receiving your protocol's frames.
Largely functional. APIs subject to change.
Using mai
requires three steps:
Frame
, an actionable message.Codec
that knows how to read and write Frame
s into byte buffers.Handler
to react to new connections, incoming Frame
s and errors.Buffer pooling, low-level reads
and writes
and Token
management are handled by mai
.
Implement the Protocol
trait by specifying the family of types you'll be using.
```rust use mai::*;
struct EchoCodec; struct EchoClientHandler; struct EchoClient;
impl Protocol for EchoClient { type ByteStream = TcpStream; // vs a UnixStream, for example type Frame = String; type Codec = EchoCodec; type Handler = EchoClientHandler; type Timeout = usize; } ```
Define methods to encode and decode your frames. Use the return codes to indicate that you got a frame, don't have enough bytes to read a frame yet or that you encountered a protocol error.
```rust
// For a simple Echo server, we can use String
as our Frame type.
// This codec would work for both a client and server connection.
impl Codec
// Provide a method to try to parse a frame from a byte buffer
fn decode(&mut self, buffer: &[u8]) -> DecodingResult
Define callbacks to handle byte stream events: connections, frames, timeouts, errors, and disconnects. ```rust use mai::*;
impl Handler
Create a ProtocolEngine
and hand it any mio
type that is Evented
+Read
+Write
. Watch it go!
``rust
fn main() {
// Create a TcpStream connected to
nc` running as an echo server
// nc -l -p 2000 -c 'xargs -n1 echo'
println!("Connecting to localhost:9999...");
let address = "0.0.0.0:9999".parse().unwrap();
let socket = TcpSocket::v4().unwrap();
let (stream, _complete) = socket.connect(&address).unwrap();
// Hand the TcpStream off to our new ProtocolEngine
configured to treat its
// byte streams as Echo clients.
let protocolengine: ProtocolEngine
Currently mai
does not have a built-in way to manage incoming connections. This is being worked on.
Running a server is conceptually a straightforward process: create a separate thread using mio to listen for incoming connections. Each time a client connection is avialable, pass the corresponding TcpStream to the ProtocolEngine running in the background. Until there is a formal API, you can get a channel to send commands to the ProtocolEngine
instance by running protocol_engine.command_sender.clone()
and sending a Command::Manage(P::ByteStream)
message that contains the ByteStream you'd like it to manage.