pub struct ServerSet { /* private fields */ }Expand description
One or more independent UDP listeners run as a single service.
Every listener is a complete Server: its own socket, its own
ServerCore, its own session table and its own tokens.
Nothing is shared between them but the configuration they were built from
and the supervision described below. A token issued by one listener is an
unknown token at any other, which is what makes the listeners genuinely
independent rather than one server wearing several addresses.
§Configuration is cloned, so bounds are per listener
The ServerConfig passed to ServerSet::bind is cloned into each
listener, so every resource bound in it applies per listener and not to
the process. A set of two listeners built from a configuration with
max_sessions of 100 admits up to 100 sessions on each, 200 in total. There
is no process-wide session cap.
§A set of one is an ordinary set
One listener is not a special case and gets no shortcut: it is bound,
supervised and shut down exactly as a member of a larger set is. The server
CLI runs every invocation through a ServerSet for that reason, so the
single-listener case exercises this path continuously.
§Supervision
run takes one shutdown future and fans it out to every
listener, then waits for all of them. A listener that fails, or that stops on
its own before shutdown was asked for, shuts its siblings down and fails the
whole set: a service configured for IPv4 and IPv6 must not quietly continue
as IPv4 only. Every listener task is joined before run returns, so no task
outlives it.
§Example
Bind independent listeners, inspect their resolved addresses, then serve them under one caller-owned shutdown future. This example type-checks without binding sockets:
use std::{net::{Ipv4Addr, SocketAddr}, time::Duration};
use irtt_server::{ServerConfig, ServerSet};
let addrs = [
SocketAddr::from((Ipv4Addr::LOCALHOST, 0)),
SocketAddr::from((Ipv4Addr::LOCALHOST, 0)),
];
let set = ServerSet::bind(addrs, ServerConfig::default()).await?;
println!("listening on {:?}", set.local_addrs());
set.run(tokio::time::sleep(Duration::from_secs(60))).await?;Implementations§
Source§impl ServerSet
impl ServerSet
Sourcepub async fn bind<I>(
addrs: I,
config: ServerConfig,
) -> Result<Self, ServerSetError>where
I: IntoIterator<Item = SocketAddr>,
pub async fn bind<I>(
addrs: I,
config: ServerConfig,
) -> Result<Self, ServerSetError>where
I: IntoIterator<Item = SocketAddr>,
Binds every requested listener, or none of them.
Requested order is preserved, and each listener is bound before any is
served: a later failure drops the sockets already opened and starts
nothing. config is cloned into every listener, so its bounds are per
listener.
A port of 0 is resolved per listener — two such requests select two
unrelated ephemeral ports, not one shared one. Ask
local_addrs for what was actually bound.
§Same-port IPv4 and IPv6
Requesting a genuine IPv6 listener and an IPv4 one on the same explicit
port — 0.0.0.0:2112 with [::]:2112, say — is a normal configuration,
but on hosts whose IPv6 sockets are dual-stack by default the IPv6 bind
would claim the IPv4 port too and the second bind would fail. Such an
IPv6 listener is therefore created IPV6_V6ONLY before it is bound, so
the two coexist and each serves its own family. Bind order is not relied
on, and a listener requested on its own is bound exactly as
Server::bind would.
An IPv4-mapped address such as [::ffff:0.0.0.0] is an IPv4 listener
here, not an IPv6 one, matching how the runtime already reads it.
A lone explicit dual-stack [::]:PORT listener is not the recommended
configuration when reply DSCP/traffic-class marking for IPv4 peers
matters: Linux serves those peers as IPv4-mapped and cannot retain the
negotiated marking on their replies. Request the normal same-port
[::]:PORT and 0.0.0.0:PORT pair instead. This method makes the IPv6
half IPv6-only for that pair only; a lone IPv6 listener retains its
normal socket behavior.
§Errors
Returns ServerSetError::NoListeners if addrs is empty — a set
serving nothing has no meaning — and
ServerSetError::ListenerSetup naming the requested address if any
listener could not be bound or prepared.
Examples found in repository?
22async fn main() {
23 let addrs = [
24 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
25 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
26 ];
27 let config = ServerConfig::default();
28
29 let set = match ServerSet::bind(addrs, config).await {
30 Ok(set) => set,
31 Err(err) => {
32 eprintln!("failed to bind listener set: {err}");
33 return;
34 }
35 };
36
37 println!("listening on {:?}", set.local_addrs());
38
39 let result = set.run(tokio::time::sleep(Duration::from_secs(2))).await;
40 if let Err(err) = result {
41 eprintln!("listener set stopped with an error: {err}");
42 }
43}Sourcepub fn local_addrs(&self) -> &[SocketAddr]
pub fn local_addrs(&self) -> &[SocketAddr]
The addresses the listeners actually bound, in requested order.
These are the resolved endpoints, so a requested port of 0 appears
here as the port the host selected.
Examples found in repository?
22async fn main() {
23 let addrs = [
24 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
25 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
26 ];
27 let config = ServerConfig::default();
28
29 let set = match ServerSet::bind(addrs, config).await {
30 Ok(set) => set,
31 Err(err) => {
32 eprintln!("failed to bind listener set: {err}");
33 return;
34 }
35 };
36
37 println!("listening on {:?}", set.local_addrs());
38
39 let result = set.run(tokio::time::sleep(Duration::from_secs(2))).await;
40 if let Err(err) = result {
41 eprintln!("listener set stopped with an error: {err}");
42 }
43}Sourcepub async fn run<F>(self, shutdown: F) -> Result<(), ServerSetError>
pub async fn run<F>(self, shutdown: F) -> Result<(), ServerSetError>
Serves every listener until shutdown completes or one of them fails.
Each listener runs in its own Tokio task and stays internally sequential
there, so the one-sender-per-socket ownership the reply traffic class
depends on is unchanged. The single shutdown future is fanned out
internally; nothing about how the caller produces it — a signal, a
channel, a timer — is this crate’s business.
The set consumes itself because each listener is moved into its task. A
caller that wants to bind and serve again binds a new set; the
lower-level Server keeps its reusable &mut self loop.
§Errors
A listener that fails, that returns before shutdown was requested, or
whose task did not complete shuts down its siblings; every remaining
listener is then drained and the first such failure is returned. An
external shutdown that completes first still returns a listener’s error
if one appears while draining. Ok(()) means every listener stopped
cleanly after being asked to.
Examples found in repository?
22async fn main() {
23 let addrs = [
24 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
25 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
26 ];
27 let config = ServerConfig::default();
28
29 let set = match ServerSet::bind(addrs, config).await {
30 Ok(set) => set,
31 Err(err) => {
32 eprintln!("failed to bind listener set: {err}");
33 return;
34 }
35 };
36
37 println!("listening on {:?}", set.local_addrs());
38
39 let result = set.run(tokio::time::sleep(Duration::from_secs(2))).await;
40 if let Err(err) = result {
41 eprintln!("listener set stopped with an error: {err}");
42 }
43}