Skip to content

Batching EN

b23r0 edited this page Oct 12, 2026 · 2 revisions

English · 简体中文

Message coalescing and batch APIs

For small messages, frame processing, system calls and scheduling can dominate the cost. A standard RakNet frame set can carry multiple complete frames, reducing datagram count while preserving application message boundaries.

Send messages that are already ready

use rust_raknet::{RaknetSocket, Reliability, error::Result};

async fn send_ready(socket: &RaknetSocket) -> Result<()> {
    let first: &[u8] = &[0xfe, 1, 10];
    let second: &[u8] = &[0xfe, 2, 20];
    socket.send_batch(&[first, second], Reliability::ReliableOrdered).await
}

The owned-buffer API uses rust_raknet::Bytes: send_bytes_batch(&[Bytes], Reliability). Both borrowed and owned APIs have *_with_order_channel variants.

When coalescing happens

  • A multi-message batch call uses ReliableOrdered.
  • Messages do not require fragmentation, and adjacent messages plus frame headers fit the negotiated MTU.
  • A datagram carries at most 8 messages, further limited by MTU. One API call may contain more messages.
  • Only ready messages are processed: there is no timer waiting to fill a batch.
  • Other modes, fragments and messages that do not fit retain individual datagrams. Two 800 B messages cannot fit together at the default MTU.

An effective NACK matching outstanding data or a reliable retransmission timeout permanently disables coalescing for that connection, until reconnecting. Loss during warmup affects later measurement phases too.

Does ordinary send() coalesce?

A fresh connection using only ordinary send() does not proactively enable coalescing; it sends promptly. Once a suitable batch call enables coalescing on that connection, queued messages from later ordinary calls may also participate. This does not introduce a timer to collect them.

Compare ordinary and batch APIs on fresh, separate connections. Do not benchmark ordinary sends by reusing a connection that already used batching.

Receive and forward batches

use rust_raknet::{RaknetSocket, Reliability, error::Result};

async fn forward(source: &RaknetSocket, destination: &RaknetSocket) -> Result<()> {
    let mut messages = Vec::with_capacity(8);
    loop {
        source.recv_bytes_batch(&mut messages, 8).await?;
        destination.send_bytes_batch(&messages, Reliability::ReliableOrdered).await?;
    }
}

recv_batch() / recv_bytes_batch() clear the output, wait for the first message, then drain only messages already available. A zero limit returns immediately; a call takes at most 64 messages. A bidirectional proxy must run both directions concurrently; see the repository proxy example.

Bytes shares immutable data and reduces application payload copies for forwarding and retransmission queues. It is not end-to-end zero-copy networking.

Batch members are validated first, but cancellation during sending can leave a prefix sent or queued. A batch is not a transaction.

Should you enable it?

Test sustained small-message workloads. Sparse traffic and larger messages may not benefit. Examine throughput, typical latency, the slowest 1% of messages and memory. In the README, batching is faster for 64 B / 1,024 connections / no injected loss, while ordinary sends are faster for 800 B / 1,024 connections.

Keep three mechanisms separate: application message coalescing, UDP sendmmsg system-call batching and optional recvmmsg receive batching. They reduce different costs.

Benchmark methodology · Implementation

Blocking API · API index · Errors and deadlines

Clone this wiki locally