mini_udp 0.3.1

A minimal implementation of a reliability protocol on top of UDP for game networking
Documentation

mini_udp

Docs License Crates.io

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 ByteRepr derive 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

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);

License

All code in this repository is dual-licensed under either

at your option.

Contributions

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.