Skip to main content

Server

Struct Server 

Source
pub struct Server { /* private fields */ }
Expand description

A Tokio UDP listener backed by one deterministic server core.

Processing is sequential: one task receives a datagram, lets the core process it, and sends the optional reply from the same socket. No per-datagram tasks or locks are involved.

That sequential ownership is what makes the reply traffic class correct. The class is a socket-wide setting, and every reply explicitly applies its own — zero included — immediately before its send, so the reply about to go out is always the one that set it. Nothing else sends from this socket, not even while a send is suspended, so no reply can inherit another session’s marking. Adding a concurrent sender would break that and require per-packet control messages instead.

§Reply source address

A reply must leave from the exact address the request was sent to. An explicit-address listener gets that from the bind: it can send from nothing else. A wildcard listener (0.0.0.0 or [::]) cannot, because the routing table would choose the source, so it asks the kernel for each request’s local destination and sends that request’s reply from it.

That per-packet path exists on Linux, macOS and FreeBSD. Elsewhere a wildcard bind is refused at construction — see ServerRuntimeError::WildcardSourceSelectionUnsupported — rather than served by a listener whose replies a client on a second address would silently discard. Explicit-address listeners are unaffected everywhere.

§Example

Bind one listener, inspect the resolved endpoint, and let the caller decide when serving ends. This example type-checks without opening a socket:

use std::{net::{Ipv4Addr, SocketAddr}, time::Duration};

use irtt_server::{Server, ServerConfig};

let mut server = Server::bind(
    SocketAddr::from((Ipv4Addr::LOCALHOST, 0)),
    ServerConfig::default(),
)
.await?;
println!("listening on {}", server.local_addr()?);
server.run(tokio::time::sleep(Duration::from_secs(60))).await?;

Implementations§

Source§

impl Server

Source

pub async fn bind( addr: SocketAddr, config: ServerConfig, ) -> Result<Self, ServerRuntimeError>

Binds one UDP listener at addr and creates its independent session namespace.

A wildcard addr additionally configures reply source selection, and fails where that is unavailable; see Server::from_socket.

Examples found in repository?
examples/basic_server.rs (line 23)
19async fn main() {
20    let bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
21    let config = ServerConfig::default();
22
23    let mut server = match Server::bind(bind_addr, config).await {
24        Ok(server) => server,
25        Err(err) => {
26            eprintln!("failed to bind server: {err}");
27            return;
28        }
29    };
30
31    println!(
32        "listening on {}",
33        server
34            .local_addr()
35            .expect("bound socket has a local address")
36    );
37
38    let result = server.run(tokio::time::sleep(Duration::from_secs(2))).await;
39    if let Err(err) = result {
40        eprintln!("server stopped with an error: {err}");
41    }
42}
Source

pub fn from_socket( socket: UdpSocket, config: ServerConfig, ) -> Result<Self, ServerRuntimeError>

Wraps an already prepared Tokio UDP socket in a server.

§Errors

Construction is fallible because a wildcard listener has setup to do before it may serve anything. It fails when the socket’s bound address cannot be queried — which is what says whether this is a wildcard listener at all — and, for a wildcard socket, when this target has no reply source selection or the kernel refuses to configure it.

Failing here is the point. A wildcard listener that cannot recover a request’s local destination would start, run, and answer clients on a second local address from an endpoint they never contacted, which they discard as though the network had dropped it.

Source

pub fn local_addr(&self) -> Result<SocketAddr, ServerRuntimeError>

Returns the local endpoint selected for the listener.

Examples found in repository?
examples/basic_server.rs (line 34)
19async fn main() {
20    let bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
21    let config = ServerConfig::default();
22
23    let mut server = match Server::bind(bind_addr, config).await {
24        Ok(server) => server,
25        Err(err) => {
26            eprintln!("failed to bind server: {err}");
27            return;
28        }
29    };
30
31    println!(
32        "listening on {}",
33        server
34            .local_addr()
35            .expect("bound socket has a local address")
36    );
37
38    let result = server.run(tokio::time::sleep(Duration::from_secs(2))).await;
39    if let Err(err) = result {
40        eprintln!("server stopped with an error: {err}");
41    }
42}
Source

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

Serves datagrams until shutdown completes.

Graceful shutdown sends no session-close packets and leaves no hidden receive task behind, even while a reply send is waiting for socket writability. Per-packet send failures and short sends drop only that reply, and so does a failure to apply a marking that reply needed — preparing one packet is not a reason to stop serving every other session, and the next reply makes its own independent attempt. Receive failures and internal core failures terminate the loop with an error.

A reply whose marking could not be applied is not sent instead: sending it would put it on the wire under the previous reply’s marking. An unmarked reply on a socket this server has never marked is the one exception, so a host that does not support the option at all — some Windows builds do not support IP_TOS — still runs a working server.

A wildcard listener drops a request whose local destination did not arrive with it, before the core sees it. There is no fall back to a routing-table source: the listener promised a correct reply source when it accepted the bind, and a request it cannot answer correctly must not move a session’s receive, rate or lifetime state either.

Examples found in repository?
examples/basic_server.rs (line 38)
19async fn main() {
20    let bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
21    let config = ServerConfig::default();
22
23    let mut server = match Server::bind(bind_addr, config).await {
24        Ok(server) => server,
25        Err(err) => {
26            eprintln!("failed to bind server: {err}");
27            return;
28        }
29    };
30
31    println!(
32        "listening on {}",
33        server
34            .local_addr()
35            .expect("bound socket has a local address")
36    );
37
38    let result = server.run(tokio::time::sleep(Duration::from_secs(2))).await;
39    if let Err(err) = result {
40        eprintln!("server stopped with an error: {err}");
41    }
42}

Trait Implementations§

Source§

impl Debug for Server

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !Freeze for Server

§

impl !RefUnwindSafe for Server

§

impl !Sync for Server

§

impl !UnwindSafe for Server

§

impl Send for Server

§

impl Unpin for Server

§

impl UnsafeUnpin for Server

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.