docs(crates): give each published crate its own crates.io README and examples
Each published crate (irtt-rs, irtt-client, irtt-server, irtt-stats, irtt-proto) now points crates.io at its own README instead of inheriting the root project README via the workspace-wide readme default, which is removed. Each crate README is a short landing page: what the crate is, who should use it, which sibling crate to use instead if this isn't the right abstraction level, and links to docs.rs and the main repository. Add real Cargo example targets teaching irtt-client's blocking/Tokio/ managed API tiers, a basic and a multi-listener irtt-server example, and an irtt-stats example contrasting finite vs continuous retention. irtt-proto intentionally has none. Update each crate's top-level rustdoc to explain its API-tier choices and point at the examples, and enable docs.rs rustdoc example scraping for irtt-client and irtt-server (via doc-scrape-examples on each example target, needed because the crate has dev-dependencies). The client examples are caller-paced against next_send_deadline() rather than sending on every loop iteration, bound their receive wait by the next send/timeout deadline instead of awaiting indefinitely, poll timeouts on a short fixed cadence, and surface send/receive/close errors instead of discarding them via unwrap_or_default()/if-let-Ok -- so they demonstrate correct pacing and terminate/report cleanly rather than racing the server, hanging on a lost reply, or silently dropping a failed final close.
This commit is contained in:
20 files changed
+751
-12
No files matched your search
@@ -7,7 +7,7 @@ license.workspace = true
|
||||
rust-version.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
readme.workspace = true
|
||||
readme = "README.md"
|
||||
keywords = ["irtt", "networking", "latency", "server"]
|
||||
categories = ["network-programming"]
|
||||
|
||||
@@ -23,3 +23,13 @@ tokio = { version = "1.53.1", default-features = false, features = ["rt", "net",
|
||||
# source-selection path here; elsewhere a wildcard bind is refused.
|
||||
[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))'.dependencies]
|
||||
nix = { version = "0.31.3", default-features = false, features = ["net", "uio"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
|
||||
|
||||
[[example]]
|
||||
name = "basic_server"
|
||||
|
||||
[[example]]
|
||||
name = "multi_listener"
|
||||
@@ -0,0 +1,41 @@
|
||||
# irtt-server
|
||||
|
||||
Reusable, Tokio-native library for running an IRTT-compatible UDP server:
|
||||
open negotiation, the bounded session table, echo processing, per-session
|
||||
rate limiting, idle and max-duration expiry, and reply construction.
|
||||
|
||||
## Looking for the CLI?
|
||||
|
||||
If you just want to run a server from the command line, use the
|
||||
[`irtt-rs`](https://crates.io/crates/irtt-rs) application package instead,
|
||||
which installs the `irtt-server` binary. This crate is the library that
|
||||
binary is built on.
|
||||
|
||||
## Structure
|
||||
|
||||
- `Server` — the one-listener primitive: one UDP socket, one deterministic
|
||||
`ServerCore`, one sequential receive/reply loop.
|
||||
- `ServerSet` — supervises one or more `Server`s as a single service: binds
|
||||
them all or none, runs each in its own task, and fans one shutdown out to
|
||||
all of them.
|
||||
|
||||
Unlike `irtt-client`, Tokio is not optional here — this crate is intentionally
|
||||
Tokio-native, with no blocking or alternate-runtime variant.
|
||||
|
||||
A wildcard listener (`0.0.0.0` / `[::]`) recovers each request's local
|
||||
destination and replies from it, so a client on a second local address is
|
||||
answered from the address it actually contacted. That path is implemented
|
||||
for Linux, macOS, and FreeBSD; a wildcard bind is refused at construction on
|
||||
other targets rather than served incorrectly. Explicit-address listeners are
|
||||
unaffected everywhere.
|
||||
|
||||
See `examples/` in the repository for runnable examples.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full API documentation is on [docs.rs/irtt-server](https://docs.rs/irtt-server).
|
||||
|
||||
## Project
|
||||
|
||||
Part of [irtt-rs](https://github.com/Lochnair/irtt-rs), an independent Rust
|
||||
implementation of an IRTT-compatible protocol stack.
|
||||
@@ -0,0 +1,42 @@
|
||||
//! Basic single-listener server example.
|
||||
//!
|
||||
//! Binds one [`Server`] on an ephemeral loopback port, serves it for a short
|
||||
//! fixed window, and shuts down. A real deployment would normally bind a
|
||||
//! fixed port and run until an external shutdown signal.
|
||||
//!
|
||||
//! Run with:
|
||||
//!
|
||||
//! ```text
|
||||
//! cargo run -p irtt-server --example basic_server
|
||||
//! ```
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::time::Duration;
|
||||
|
||||
use irtt_server::{Server, ServerConfig};
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
|
||||
let config = ServerConfig::default();
|
||||
|
||||
let mut server = match Server::bind(bind_addr, config).await {
|
||||
Ok(server) => server,
|
||||
Err(err) => {
|
||||
eprintln!("failed to bind server: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!(
|
||||
"listening on {}",
|
||||
server
|
||||
.local_addr()
|
||||
.expect("bound socket has a local address")
|
||||
);
|
||||
|
||||
let result = server.run(tokio::time::sleep(Duration::from_secs(2))).await;
|
||||
if let Err(err) = result {
|
||||
eprintln!("server stopped with an error: {err}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//! Multi-listener example using [`ServerSet`].
|
||||
//!
|
||||
//! `ServerSet` binds several independent [`Server`] listeners as one service:
|
||||
//! all-or-nothing bind, one shutdown fanned out to every listener, and the
|
||||
//! whole set fails if any listener does. Each listener still gets its own
|
||||
//! socket, session table, and tokens — a token issued by one is unknown at
|
||||
//! the other. This is what the `irtt-rs` server CLI always runs through, even
|
||||
//! for a single bind.
|
||||
//!
|
||||
//! Run with:
|
||||
//!
|
||||
//! ```text
|
||||
//! cargo run -p irtt-server --example multi_listener
|
||||
//! ```
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::time::Duration;
|
||||
|
||||
use irtt_server::{ServerConfig, ServerSet};
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let addrs = [
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
|
||||
];
|
||||
let config = ServerConfig::default();
|
||||
|
||||
let set = match ServerSet::bind(addrs, config).await {
|
||||
Ok(set) => set,
|
||||
Err(err) => {
|
||||
eprintln!("failed to bind listener set: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!("listening on {:?}", set.local_addrs());
|
||||
|
||||
let result = set.run(tokio::time::sleep(Duration::from_secs(2))).await;
|
||||
if let Err(err) = result {
|
||||
eprintln!("listener set stopped with an error: {err}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user