Expand description
§Overview
A minimal, fully synchronous implementation of a reliability protocol on top of UDP.
This was inspired by Glenn Fiedler, who wrote an amazing set of articles about this: https://gafferongames.com/categories/building-a-game-network-protocol/
The main entry points of this crate are UdpCommunicator
and MultiUdpCommunicator, which both wrap
std::net::UdpSocket.
All messages are (de)serialized by the in-house ByteRepr trait, which has a derive macro as well:
ByteRepr.
§Features
- Derive byte representations for Enums and Structs.
- Send unreliable, reliable, or reliable ordered messages over UDP.
- Verify packet integrity using a 4-byte CRC, seeded with a user-defined protocol version.
-
Messages get combined into packets, with a maximum packet size of 1024 bytes
(
MAX_PACKET_DATA_LEN). -
Handle 1-X communication via a single, shared UDP socket
(
MultiUdpCommunicator). - Have a fully synchronous, non-blocking API, ideal for game networking.
- Messages cannot be fragmented yet, so it is not possible to send messages larger than 1024 bytes! (yet)
-
The
ByteReprderive is not very mindful of bandwidth yet (booleans are padded to 1 byte, strings and vecs use 4 bytes to send their length as u32).
§Example
Show example
use mini_udp::prelude::*;
#[derive(ByteRepr, Debug, PartialEq)]
enum MessageToServer {
Hello,
Position([f32; 3]),
}
#[derive(ByteRepr, Debug)]
enum MessageToClient {
WhatIsYourPosition,
Bye,
}
/// The protocol version is used as seed for the CRC algorithm. Thus, when receiving a packet
/// that was send from a communicator with a different version, the CRC check will fail.
const PROTOCOL_VERSION: u32 = 1;
const POSITION: [f32; 3] = [-1., 0.004, 2482.3];
let mut server = MultiUdpCommunicator::<_, _, PROTOCOL_VERSION>::bind("0.0.0.0:7001");
// `UdpCommunicator::default()` binds the communicator to "0.0.0.0:0", which lets the OS decide
// which port to use.
let mut client =
UdpCommunicator::<_, _, PROTOCOL_VERSION>::default().connect("0.0.0.0:7001").unwrap();
// The `write*` methods only add the message to a queue, they won't be send until you explicitly
// call `send()`.
client.write_ordered(MessageToServer::Hello);
let mut messages_read = 0;
loop {
// Send all queued messages. This is also responsible for resending reliable packets if
// they have not received an acknowledgement yet.
client.send().unwrap();
// Receive all new packets. You can provide a callback function that will be called for each
// received packet, with a mutable reference to the associated connection.
server.recv(|mut com: UdpCommunicatorMut<_, _, _>| {
if let Some(msg) = com.read_ordered() {
messages_read += 1;
match msg {
MessageToServer::Hello =>
com.write_ordered(MessageToClient::WhatIsYourPosition),
MessageToServer::Position(pos) => {
assert_eq!(pos, POSITION);
com.write_ordered(MessageToClient::Bye);
}
}
}
});
server.send();
client.recv();
// If we would call `client.read()` here, we would not get any messages because ordered and
// non-ordered receive queues are separated.
if let Some(msg) = client.read_ordered() {
messages_read += 1;
match msg {
MessageToClient::WhatIsYourPosition => {
client.write_ordered(MessageToServer::Position(POSITION));
}
MessageToClient::Bye => break,
}
}
}
assert_eq!(messages_read, 4);Modules§
- communicator
- packet
- prelude
- ring_
buffer - The ring buffer implementation used to cache reliably send and received packets.
Enums§
Traits§
- Byte
Repr - You may use the derive macro to implement this trait.
- Static
Byte Repr