blocking_client/blocking_client.rs
1//! Runtime-free blocking client example.
2//!
3//! Opens a session against a local IRTT-compatible server, sends a few
4//! probes, and prints the events observed. Uses only [`Client`], which has no
5//! Tokio dependency and works in a default (`--no-default-features`) build.
6//!
7//! Run a server first, then run this example:
8//!
9//! ```text
10//! cargo run -p irtt-rs --bin irtt-server --features server
11//! cargo run -p irtt-client --example blocking_client
12//! ```
13//!
14//! Without a reachable server this exits quickly with an open-timeout error
15//! instead of hanging, because the example shortens `open_timeouts` for a
16//! fast demonstration; a real caller would normally keep the default retry
17//! schedule.
18
19use std::time::{Duration, Instant};
20
21use irtt_client::{Client, ClientConfig, ClientEvent};
22
23const RUN_DURATION: Duration = Duration::from_secs(2);
24
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}
101
102fn print_event(event: &ClientEvent) {
103 match event {
104 ClientEvent::EchoReply { seq, rtt, .. } => {
105 println!("seq {seq}: rtt {:?}", rtt.effective);
106 }
107 ClientEvent::EchoLoss { seq, .. } => println!("seq {seq}: lost"),
108 other => println!("{other:?}"),
109 }
110}