Skip to main content

ServerSet

Struct ServerSet 

Source
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

Source

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?
examples/multi_listener.rs (line 29)
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}
Source

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?
examples/multi_listener.rs (line 37)
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}
Source

pub async fn run<F>(self, shutdown: F) -> Result<(), ServerSetError>
where F: Future<Output = ()>,

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?
examples/multi_listener.rs (line 39)
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}

Trait Implementations§

Source§

impl Debug for ServerSet

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.