Repository navigation
Send Policy EN
Availability: included in 1.1.0, published on October 3, 2026. The measured implementation originated in 8c06cf4, now part of the public release. Older releases do not provide this feature.
Enable the feature on the released crate:
[dependencies]
rust-raknet = { version = "1.4.0", features = ["send-policy"] }The feature exposes SendOptions, SendOptionsError, set_send_options and send_options; it does not activate the policy on every socket automatically.
use rust_raknet::{RaknetSocket, SendOptions};
async fn configure(socket: &RaknetSocket) -> Result<(), Box<dyn std::error::Error>> {
let options = SendOptions::default()
.with_in_flight_limits(64, 256 * 1024)?
.with_queue_budgets(256 * 1024, 64 * 1024)?
.with_flush_budget(256 * 1024)?;
socket.set_send_options(options).await?;
assert_eq!(socket.send_options().await, Some(options));
Ok(())
}Apply to connected client sockets and accepted server sockets before bulk sending. Options are shared by clones of the same connection. A feature-enabled socket returns None until configured. Default builds do not compile policy state or checks and do not expose these methods.
The configure function above can be applied immediately after connecting:
let address: std::net::SocketAddr = "127.0.0.1:19132".parse()?;
let client = RaknetSocket::connect(&address).await?;
configure(&client).await?;On the server, configure each socket returned by accept, before starting its sender task. This is a socket API, not a listener option:
let client = listener.accept().await?;
configure(&client).await?;For a reverse proxy, the accepted frontend and the connected upstream are separate connections. Configure both if both sending directions need the policy. Configuring one leg does not configure the other, and no policy is negotiated with the remote peer.
let frontend = listener.accept().await?;
let upstream = RaknetSocket::connect(&upstream_address).await?;
configure(&frontend).await?;
configure(&upstream).await?;These fragments assume a listening RaknetListener, an upstream SocketAddr where needed, and an async function that propagates errors. The setting changes future admission and scheduling, not message reliability: continue selecting Reliability with each send.
Preserve other configured values when changing a limit:
let previous = socket.send_options().await.unwrap_or_default();
let updated = previous.with_in_flight_limits(32, 64 * 1024)?;
socket.set_send_options(updated).await?;If the socket was unconfigured, this activates the policy. If it was already configured, unchanged queue and flush budgets remain in place. Lowering limits preserves accepted messages and outstanding frames; stricter admission takes effect as old work drains. The peer can receive a short remaining burst from earlier accepted work, so this is not an instantaneous network-rate limiter.
There is no disable/reset-to-unconfigured API. set_send_options(SendOptions::default()) applies a preset; it does not turn off the scheduler. A new unconfigured socket starts with the original strategy, and a build without the feature excludes the policy code.
| Goal | Start with | What to watch |
|---|---|---|
| Reliable-only throughput | Default build; no policy setter | No need to add policy costs when you do not need its controls |
| Mixed reliable and realtime updates | Explicit SendOptions::default() and separate sender tasks |
Delivery rate and reliable throughput as well as latency of received updates |
| Less outstanding reliable data | Reduce frame or byte limit one at a time | Healthy high-RTT throughput can fall; packet loss alone does not establish queue congestion |
| Smaller bursts | Reduce flush_bytes cautiously |
Remaining frames may wait for another send, ACK or maintenance tick |
| More queued work | Adjust the reliable soft budget or unreliable reserve | Queueing delay, memory and application backpressure; these are not bandwidth allocations |
Frame and byte limits apply together: whichever is reached first stops new reliable flight. For a rough starting estimate, bandwidth × RTT gives a byte budget for data in transit (100 Mbps × 30 ms ≈ 375,000 B). This is not an automatic controller: application windows, ACK behavior, encoded-frame overhead and shared bottlenecks still matter. Queue budgets and the in-flight limits are different constraints.
The 256 KiB flight-byte cap in the earlier example is a custom configuration. The benchmark policy columns use SendOptions::default() with 64 MiB; do not attach those measurements to the custom cap.
| Limit | SendOptions::default() |
Meaning |
|---|---|---|
| Reliable flight frames | 64 | Outstanding reliable frames, including fragments and individual packed members |
| Reliable flight bytes | 64 MiB | Encoded frame bytes, including frame-set headers |
| Reliable queue soft budget | 256 KiB | Pending and unacknowledged reliable payload plus estimated metadata |
| Unreliable queue reserve | 64 KiB | Independent pending budget; newly accepted reliable data must leave it free |
| Flush budget | 256 KiB | Encoded frame-byte burst returned by one scheduled flush |
Frame limits accept 1–4096. Flight and flush byte limits accept 2048 B–64 MiB. Each queue budget must be at least 2048 B, and their sum cannot exceed 64 MiB. Applying options also checks the negotiated MTU. Builders return SendOptionsError; applying incompatible limits returns a transport error.
These are accounting limits, not process RSS or bandwidth caps. A larger reliable send, including a batch, may exceed the reliable soft budget but remains subject to the 64 MiB hard accounting limit and unreliable reserve. Unreliable batches larger than their reserve stream through the existing send path rather than requiring the entire batch to fit at once.
Configured connections keep independent pending queues. A full reliable flight window does not prevent admission of a small unreliable update while its own reserve is available. Flush scheduling rotates in byte-sized quanta between unreliable frames, reliable retransmissions and new reliable frames, subject to flight and burst limits.
Use separate application sender tasks if unreliable updates must proceed while another task is waiting for reliable capacity. The scheduler does not replace or silently evict accepted unreliable messages. The network can still lose them; UnreliableSequenced retains its existing sequence-filtering semantics.
Lowering limits preserves already accepted messages; they drain before further admission. A very small flush budget may defer remaining work to the next send, ACK or existing maintenance tick. There is no new timer to wait for a batch.
The wire encoding, five reliability modes, fragmentation, ACK/NACK handling and retransmission rules remain unchanged. Options require no new peer negotiation. The policy is not adaptive congestion control, does not guarantee a bandwidth split and does not expand or shrink windows automatically.
A smaller window can reduce bottleneck queue pressure while also limiting healthy high-RTT throughput. Choose it using your workload and network tests rather than assuming a smaller window is always better.
The October 3 benchmark compares default and explicitly configured ordinary/batch builds against TCP, C KCP and quic-go. It contains 623 verified measurements with ranges and memory. All profiles use reliable ordered echoes; they do not measure mixed-traffic scheduling benefits.
The policy changes queue admission and burst timing, with extra accounting and scheduling work where applicable. It can affect throughput and slow-message latency while preserving the wire protocol. The benchmark does not isolate the cause of each change, and a feature-enabled but unconfigured build is not the default binary measured in the tables.
| Reliable ordered profile | Default build | Configured preset | Median change |
|---|---|---|---|
| One connection, 800 B, no injected loss, ordinary throughput | 185.57 MiB/s | 175.19 MiB/s | −5.6% |
| 1,024 connections, 800 B, no injected loss, ordinary throughput | 464.75 MiB/s | 486.67 MiB/s | +4.7% |
| 1,024 connections, 800 B, no injected loss, batch throughput | 481.91 MiB/s | 423.69 MiB/s | −12.1% |
| 1,024 connections, 64 B, no injected loss, ordinary loaded 99th-percentile RTT | 54.03 ms | 100.53 ms | +86.1% (slower) |
These are medians from three repeats; the full result tables show ranges and RSS. They are not proof of statistical significance or universal improvements.
A separate mixed-traffic experiment used eight one-connection processes sharing a link that dropped from 100 Mbps / 30 ms RTT to 2 Mbps / 800 ms RTT. Candidate senders used the default policy preset; each saturated a reliable 896 B stream while a separate task produced 64 B unreliable updates at 100 Hz. During congestion, slowest-1% latency among delivered unreliable messages improved from 1,894.73 to 743.49 ms, while reliable throughput fell from 0.072704 to 0.059392 Mbps. Whole-run unreliable delivery fell from 68.65% to 60.01%. Lower delivered-message latency must be considered alongside lost messages and reliable bandwidth.
Keep the feature optional, use separate mixed sender tasks, and measure your payloads, connection counts and network. Neither more buffering nor smaller windows guarantees higher throughput and lower latency at the same time.
Default and feature test builds passed 118 and 144 checks respectively, with overlapping baseline tests. Automated Bedrock 26.52 handshake/NetworkSettings and proxy checks passed 24/24; these were protocol checks, not a new manual gameplay test.
Configuration and concurrency · Benchmark methodology · Reproduction
For recovery timing and tail-loss options introduced in 1.2.0, see recovery policy. The parameter reference covers both independent features. Use the published 1.4.0 dependency for all current examples.
English · 简体中文 · GitHub · crates.io 1.4.0 · Reviewed / 核对:2026-10-12 / b909c93
- Overview
- Quick start
- Blocking calls and shared runtimes
- Public API index
- Five reliability modes
- RakNet wire format and recovery
- Message lifecycle and limits
- Bytes and explicit batching
- Send policy
- Recovery policy and tail probes
- Every configuration parameter
- Concurrency and socket tuning
- Errors, deadlines and cancellation
- Congestion and deployment boundaries
- Benchmark results
- Benchmark methodology
- Benchmark reproduction
- Minecraft Bedrock proxy
- Troubleshooting and development
- Release history and upgrading