Library / SDK
lemunozm/message-io avatar
lemunozm/message-io

message-io: An Event Driven Network Library for Rust

Fast and easy-to-use event-driven network library.

1,197 stars84 forksRustApache-2.0

At a glance

What is it?
message-io is a Rust library that hides the operating system socket behind an event and message API, so an application deals in messages and endpoints rather than in threads, buffers and IO errors. It supports TCP, UDP and WebSocket behind one interface, with non-blocking sockets, zero-copy writes and a single-threaded event loop.
Who is it for?
message-io fits a Rust project that needs a socket server or client without building its own event loop, and that wants to switch between TCP, UDP and WebSocket without touching its application code, since the transport is chosen at the listen call.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 36 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two Concepts Instead of a Socket API

message-io is described as a fast and easy-to-use event-driven network library, and the way it frames the problem is what makes it worth understanding.

Managing sockets is hard because you need to fight with threads, concurrency, full duplex, encoding and the IO errors that come from the operating system, which are really difficult to understand in some situations. Making sockets non-blocking adds another layer, because you then have to synchronise the events that arrive asynchronously from the OS.

The library's answer is to make all of that transparent and to reduce the surface to two concepts. Messages are the data you send and receive, and endpoints are the recipients of that data. The README says this abstraction also offers the possibility of using the same API independently of the transport protocol used, and that you could change the transport of your application in literally one line.

That last claim is the design target, and it is the one to test when you evaluate. If your application code only ever deals in messages and endpoints, then TCP, UDP and WebSocket become a configuration decision rather than a rewrite, and that is the difference between a networking library you can live with and one that dictates the shape of your application.

One Thread to Rule Them All

The concurrency model is described in the feature list under the heading of an easy, intuitive and consistent API, and it is the part that surprises people who arrive from a thread-per-connection design.

You forget concurrency problems because you handle all connections and listeners from one thread, in the phrase the README uses, one thread to rule them all. The mechanics are the handler and the listener. A NodeHandler manages all the connections, with connect, listen, remove and send, and it also handles the signals, meaning timers and priority. A NodeListener processes all the signals and events from the network.

So there are two objects with two roles. The handler is where you make things happen, and the listener is where you find out what happened. The two are separated, and the listener is where the event loop lives.

Error handling is part of the same simplification. The README says not to deal with dark internal std::io::Error when sending or receiving from the network, and the point is that a network event arriving at the listener carries a result you can inspect as part of the event, rather than an error that has to be matched against the operation that caused it. In a system where a single thread handles thousands of connections, the per-operation error context is genuinely hard to reconstruct, and folding it into the event solves it.

The library also offers custom FIFO events with timers and priority, which is how you schedule work of your own inside the same loop.

TCP, UDP and WebSocket Behind One API

The transport list is short and the differences between the options are the ones you need to understand before choosing.

TCP comes in stream and framed mode, and the framed mode is the one to pay attention to, because it deals with messages instead of a stream. A stream transport gives you bytes and leaves the framing to you, while a framed transport gives you a message per read, which is what a message-oriented API needs.

UDP is supported with a multicast option, and the multicast example in the repository shows what that looks like in practice.

WebSocket is supported in the plain variant. The secure option is struck through in the README and points at issue 102, and the implementation uses tungstenite-rs. The README also notes that wasm is not supported but is planned, with issue 100, so a WebAssembly target is not currently a possibility.

The platform story is inherited from mio, and the README links to the mio platform support page rather than claiming a list of its own, which is the honest way to handle it since the actual set of supported systems is whatever the underlying polling library supports.

Performance is claimed in two specific ways. Messages are written and read with zero-copy, meaning you write and read directly from the internal OS socket buffer without any copy in the middle by the library. And the transport is full duplex, with simultaneous reading and writing over the same internal socket, which is the property a request and response protocol on one connection depends on.

Features: Only the Transports You Actually Need

The dependency configuration is a normal Cargo feature setup, and the default pulls in everything.

toml
[dependencies]
message-io = "0.19"

The default feature set is tcp, udp and websocket, all included by default. If you only want part of the transport battery, you select them by their feature names, and the README gives the example of including only TCP and UDP:

toml
[dependencies]
message-io = { version = "0.19", default-features = false, features = ["tcp", "udp"] }

The features are wired to their underlying libraries in the manifest. The tcp and udp features enable mio/net and socket2, and the websocket feature pulls in tungstenite, url and tcp, so a WebSocket build necessarily includes the TCP transport.

The dependency set beyond that is small and deliberate. mio 0.8 with the os-poll feature provides the event polling, serde with derive is there for the message types, crossbeam-channel and crossbeam-utils carry the internal channel communication, log and strum handle logging and the enum plumbing, socket2 covers the socket options, integer-encoding handles the protocol integers, and lazy_static holds the global state. On Linux the manifest adds nix with the socket, uio and net features, and on Unix it adds libc.

So the crate compiles on the platforms mio supports, and the OS specific parts are confined to one target section of the manifest.

An Echo Server in Three Transport Modes

The example in the README is the clearest description of the whole API, and it is a server that offers the same service over three protocols at once.

The node is created in two parts, the handler for actions and the listener for events, and then the transports are registered:

rust
handler.network().listen(Transport::FramedTcp, "0.0.0.0:3042").unwrap();
handler.network().listen(Transport::Udp, "0.0.0.0:3043").unwrap();
handler.network().listen(Transport::Ws, "0.0.0.0:3044").unwrap();

The listener then consumes the network events, and the event type is the discriminant that tells you what happened. A Connected event is used for explicit connections, an Accepted event reports a client connecting on TCP or WebSocket, a Message event carries the endpoint and the data, and a Disconnected event reports a client going away.

The message arm is where the design pays off. The handler sends the data straight back to the endpoint it came from, and because the endpoint is the abstraction, that same line works whether the message arrived over framed TCP, UDP or WebSocket. There is no transport branch anywhere in the handler.

The example is marked as no_run in the README, since it is illustrative, and the repository also carries a full example directory with a ping-pong client and server, a multicast example, a distributed network with a discovery server, a file transfer example and a throughput example.

Custom Adapters for a Protocol of Your Own

The custom adapter is the extension point, and it is what stops the library from being limited to the transports it ships with.

The library handles the OS socket internally and offers a simple event message API to the user, and it also allows you to make an adapter for your own transport protocol following some rules, delegating the tedious asynchrony and thread management to the library. The feature list says it plainly: if message-io does not have the transport you need, you can easily add an adapter.

The division of labour is the point. The library keeps the non-blocking socket handling, the event loop, the thread management and the message framing, and your adapter provides the part that is specific to your protocol. So a binary protocol with a header of your own, or a transport over something unusual, becomes a small amount of code rather than a rewrite of the networking layer.

The documentation lists the basic concepts page, the API documentation on docs.rs, the performance benchmarks document and the examples, and the benchmarks are in the repository as a benches/ directory with a latency benchmark using the criterion harness, which is the reference point for the performance claims in the README rather than a marketing number.

The Project State and What to Verify

The crate metadata is conventional and mostly informative. The package is message-io at version 0.19.0, edition 2018, with the Apache-2.0 licence, and the documentation on docs.rs is built with all features enabled. The manifest carries a maintenance badge with the status actively-developed, and the repository is not archived with the last push on 2026-09-17.

There are no GitHub releases, so the version in Cargo.toml is the reference point, and since it is a 0.x version the semver rules do not promise a stable interface, which is the usual reason to pin the exact version in your manifest.

Three things are worth verifying before you commit to the library, and they come straight from the README rather than from general knowledge. The first is the WebSocket secure option, which is marked as unavailable with issue 102 tracking it, so a WebSocket service that must use TLS is not covered by the current feature set. The second is WebAssembly, which is planned but not supported, with issue 100. The third is the platform list, which is inherited from mio, so if you deploy on an unusual platform that is the page to read.

And the one to check first of all is the basic concepts document, because the whole model rests on messages and endpoints, and a design that does not fit how you think about connections will cost you more than any of the missing features.

Editorial conclusion

message-io fits a Rust project that needs a socket server or client without building its own event loop, and that wants to switch between TCP, UDP and WebSocket without touching its application code, since the transport is chosen at the listen call. It is a poor fit if you need WebSocket over TLS, since the secure option is marked as not currently available in the feature list with a tracking issue, and a poor fit if you need to run in a WebAssembly environment, which the README lists as planned but not supported. Before adopting it, read the basic concepts document and the benchmarks, and note that the crate is at version 0.19 with a 0.x version number and no GitHub releases, so pin the version in Cargo.toml rather than tracking a branch. The project is Apache-2.0 licensed and the last push was on 2026-09-17.

Frequently asked questions

What is message-io and what abstraction does it use?

It is a Rust event driven network library that handles the OS socket internally and exposes a simple event message API built on two concepts, messages for the data you send and receive, and endpoints for the recipients of that data. The same API works independently of the transport protocol.

Which transports does message-io support?

TCP in stream and framed mode, UDP with a multicast option, and WebSocket in the plain variant using tungstenite-rs. The secure WebSocket option is marked as not currently available with issue 102, and wasm is planned but not supported per issue 100.

How do I install message-io with only some transports?

Add `message-io = "0.19"` to Cargo.toml for all transports, or turn the defaults off and list the features you want, for example `message-io = { version = "0.19", default-features = false, features = ["tcp", "udp"] }`. The available feature names are tcp, udp and websocket.

How does message-io handle concurrency?

All connections and listeners are handled from one thread. A NodeHandler manages the connections and the signals such as timers and priority, and a NodeListener processes the events coming from the network, so the event loop lives in the listener.

Can I use my own transport protocol with message-io?

Yes. The library allows you to write an adapter for your own transport protocol following the documented rules, which delegates the asynchronous handling and thread management to the library while you provide the protocol specific part.

Official sources

  1. Issues
  2. lemunozm/message-io on GitHub
  3. License: Apache-2.0
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lemunozm-message-io.svg)](https://hysenlabs.com/projects/lemunozm-message-io)

Was this useful?