pub struct AsyncClient { /* private fields */ }Expand description
Low-level Tokio IRTT client for one connected UDP target.
AsyncClient does not construct, own, or store a Tokio runtime. Its async
methods are polled by the caller and require a current Tokio runtime with I/O
and time enabled.
§Example
Use this low-level adapter when the application already owns Tokio and wants to drive the session lifecycle itself. This example type-checks without contacting a server.
use irtt_client::{AsyncClient, ClientConfig};
let mut client = AsyncClient::connect(ClientConfig::default()).await?;
let outcome = client.open().await?;
println!("opened: {outcome:?}");
let sent = client.send_probe().await?;
println!("sent: {sent:?}");
let received = client.recv().await?;
println!("received: {received:?}");
let timed_out = client.poll_timeouts()?;
println!("timeouts: {timed_out:?}");
let closed = client.close().await?;
println!("closed: {closed:?}");Implementations§
Source§impl AsyncClient
impl AsyncClient
Sourcepub async fn connect(config: ClientConfig) -> Result<Self, ClientError>
pub async fn connect(config: ClientConfig) -> Result<Self, ClientError>
Resolve the configured server and construct one connected Tokio UDP socket.
Polling this future without a current Tokio runtime returns
ClientError::NoTokioRuntime. A runtime without enabled I/O or time
drivers is outside this type’s runtime contract.
Examples found in repository?
42async fn run() {
43 let config = ClientConfig {
44 server_addr: "127.0.0.1:2112".to_owned(),
45 duration: Some(Duration::from_secs(2)),
46 interval: Duration::from_millis(200),
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 open_timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 };
52
53 let mut client = match AsyncClient::connect(config).await {
54 Ok(client) => client,
55 Err(err) => {
56 eprintln!("failed to prepare client socket: {err}");
57 return;
58 }
59 };
60
61 match client.open().await {
62 Ok(outcome) => println!("session opened: {outcome:?}"),
63 Err(err) => {
64 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65 return;
66 }
67 }
68
69 while !client.is_run_complete() {
70 // Bound the receive wait by the next send/timeout deadline, rather
71 // than awaiting recv() unconditionally: a lost or rate-limited reply
72 // must not stall pacing or timeout classification indefinitely.
73 let wake_at = client
74 .next_send_deadline()
75 .into_iter()
76 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
77 .min()
78 .expect("the fixed poll-interval deadline is always present");
79
80 let recv_result = tokio::select! {
81 events = client.recv() => Some(events),
82 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
83 };
84 if let Some(result) = recv_result {
85 match result {
86 Ok(events) => events.iter().for_each(print_event),
87 Err(err) => {
88 eprintln!("receive failed: {err}");
89 break;
90 }
91 }
92 }
93
94 if client
95 .next_send_deadline()
96 .is_some_and(|send_at| Instant::now() >= send_at)
97 {
98 // A failed send retains its prepared probe without advancing the
99 // schedule, so a persistent error (e.g. the interface going
100 // down) would otherwise make this loop retry forever instead of
101 // reporting it.
102 match client.send_probe().await {
103 Ok(events) => events.iter().for_each(print_event),
104 Err(err) => {
105 eprintln!("send failed: {err}");
106 break;
107 }
108 }
109 }
110 match client.poll_timeouts() {
111 Ok(events) => events.iter().for_each(print_event),
112 Err(err) => {
113 eprintln!("timeout polling failed: {err}");
114 break;
115 }
116 }
117 }
118
119 match client.close().await {
120 Ok(events) => events.iter().for_each(print_event),
121 Err(err) => eprintln!("close failed: {err}"),
122 }
123}Sourcepub async fn open(&mut self) -> Result<OpenOutcome, ClientError>
pub async fn open(&mut self) -> Result<OpenOutcome, ClientError>
Perform the IRTT open exchange.
Each configured attempt uses one absolute deadline for one successful request submission and all replies inspected during that attempt. Malformed, unrelated, wrong-direction, and unauthenticated datagrams are ignored without resending the request.
Dropping this future before trusted acceptance leaves the local machine connected with no committed schedule or negotiated adapter state. If it is dropped during best-effort post-token cleanup, cleanup may remain incomplete because this low-level client never detaches cleanup work.
Examples found in repository?
42async fn run() {
43 let config = ClientConfig {
44 server_addr: "127.0.0.1:2112".to_owned(),
45 duration: Some(Duration::from_secs(2)),
46 interval: Duration::from_millis(200),
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 open_timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 };
52
53 let mut client = match AsyncClient::connect(config).await {
54 Ok(client) => client,
55 Err(err) => {
56 eprintln!("failed to prepare client socket: {err}");
57 return;
58 }
59 };
60
61 match client.open().await {
62 Ok(outcome) => println!("session opened: {outcome:?}"),
63 Err(err) => {
64 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65 return;
66 }
67 }
68
69 while !client.is_run_complete() {
70 // Bound the receive wait by the next send/timeout deadline, rather
71 // than awaiting recv() unconditionally: a lost or rate-limited reply
72 // must not stall pacing or timeout classification indefinitely.
73 let wake_at = client
74 .next_send_deadline()
75 .into_iter()
76 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
77 .min()
78 .expect("the fixed poll-interval deadline is always present");
79
80 let recv_result = tokio::select! {
81 events = client.recv() => Some(events),
82 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
83 };
84 if let Some(result) = recv_result {
85 match result {
86 Ok(events) => events.iter().for_each(print_event),
87 Err(err) => {
88 eprintln!("receive failed: {err}");
89 break;
90 }
91 }
92 }
93
94 if client
95 .next_send_deadline()
96 .is_some_and(|send_at| Instant::now() >= send_at)
97 {
98 // A failed send retains its prepared probe without advancing the
99 // schedule, so a persistent error (e.g. the interface going
100 // down) would otherwise make this loop retry forever instead of
101 // reporting it.
102 match client.send_probe().await {
103 Ok(events) => events.iter().for_each(print_event),
104 Err(err) => {
105 eprintln!("send failed: {err}");
106 break;
107 }
108 }
109 }
110 match client.poll_timeouts() {
111 Ok(events) => events.iter().for_each(print_event),
112 Err(err) => {
113 eprintln!("timeout polling failed: {err}");
114 break;
115 }
116 }
117 }
118
119 match client.close().await {
120 Ok(events) => events.iter().for_each(print_event),
121 Err(err) => eprintln!("close failed: {err}"),
122 }
123}Sourcepub async fn send_probe(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub async fn send_probe(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Send one caller-paced probe with transactional protocol and schedule commits.
A prepared packet is retained across readiness false positives, cancellation, and socket errors so a later call retries the same logical probe without advancing state before kernel acceptance.
Examples found in repository?
42async fn run() {
43 let config = ClientConfig {
44 server_addr: "127.0.0.1:2112".to_owned(),
45 duration: Some(Duration::from_secs(2)),
46 interval: Duration::from_millis(200),
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 open_timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 };
52
53 let mut client = match AsyncClient::connect(config).await {
54 Ok(client) => client,
55 Err(err) => {
56 eprintln!("failed to prepare client socket: {err}");
57 return;
58 }
59 };
60
61 match client.open().await {
62 Ok(outcome) => println!("session opened: {outcome:?}"),
63 Err(err) => {
64 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65 return;
66 }
67 }
68
69 while !client.is_run_complete() {
70 // Bound the receive wait by the next send/timeout deadline, rather
71 // than awaiting recv() unconditionally: a lost or rate-limited reply
72 // must not stall pacing or timeout classification indefinitely.
73 let wake_at = client
74 .next_send_deadline()
75 .into_iter()
76 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
77 .min()
78 .expect("the fixed poll-interval deadline is always present");
79
80 let recv_result = tokio::select! {
81 events = client.recv() => Some(events),
82 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
83 };
84 if let Some(result) = recv_result {
85 match result {
86 Ok(events) => events.iter().for_each(print_event),
87 Err(err) => {
88 eprintln!("receive failed: {err}");
89 break;
90 }
91 }
92 }
93
94 if client
95 .next_send_deadline()
96 .is_some_and(|send_at| Instant::now() >= send_at)
97 {
98 // A failed send retains its prepared probe without advancing the
99 // schedule, so a persistent error (e.g. the interface going
100 // down) would otherwise make this loop retry forever instead of
101 // reporting it.
102 match client.send_probe().await {
103 Ok(events) => events.iter().for_each(print_event),
104 Err(err) => {
105 eprintln!("send failed: {err}");
106 break;
107 }
108 }
109 }
110 match client.poll_timeouts() {
111 Ok(events) => events.iter().for_each(print_event),
112 Err(err) => {
113 eprintln!("timeout polling failed: {err}");
114 break;
115 }
116 }
117 }
118
119 match client.close().await {
120 Ok(events) => events.iter().for_each(print_event),
121 Err(err) => eprintln!("close failed: {err}"),
122 }
123}Sourcepub async fn recv(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub async fn recv(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Await and classify one complete UDP datagram.
State is validated before readiness is awaited. Readiness false positives retry without changing protocol state. Authenticated peer-close events remain authoritative even if best-effort DSCP cleanup fails.
Examples found in repository?
42async fn run() {
43 let config = ClientConfig {
44 server_addr: "127.0.0.1:2112".to_owned(),
45 duration: Some(Duration::from_secs(2)),
46 interval: Duration::from_millis(200),
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 open_timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 };
52
53 let mut client = match AsyncClient::connect(config).await {
54 Ok(client) => client,
55 Err(err) => {
56 eprintln!("failed to prepare client socket: {err}");
57 return;
58 }
59 };
60
61 match client.open().await {
62 Ok(outcome) => println!("session opened: {outcome:?}"),
63 Err(err) => {
64 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65 return;
66 }
67 }
68
69 while !client.is_run_complete() {
70 // Bound the receive wait by the next send/timeout deadline, rather
71 // than awaiting recv() unconditionally: a lost or rate-limited reply
72 // must not stall pacing or timeout classification indefinitely.
73 let wake_at = client
74 .next_send_deadline()
75 .into_iter()
76 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
77 .min()
78 .expect("the fixed poll-interval deadline is always present");
79
80 let recv_result = tokio::select! {
81 events = client.recv() => Some(events),
82 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
83 };
84 if let Some(result) = recv_result {
85 match result {
86 Ok(events) => events.iter().for_each(print_event),
87 Err(err) => {
88 eprintln!("receive failed: {err}");
89 break;
90 }
91 }
92 }
93
94 if client
95 .next_send_deadline()
96 .is_some_and(|send_at| Instant::now() >= send_at)
97 {
98 // A failed send retains its prepared probe without advancing the
99 // schedule, so a persistent error (e.g. the interface going
100 // down) would otherwise make this loop retry forever instead of
101 // reporting it.
102 match client.send_probe().await {
103 Ok(events) => events.iter().for_each(print_event),
104 Err(err) => {
105 eprintln!("send failed: {err}");
106 break;
107 }
108 }
109 }
110 match client.poll_timeouts() {
111 Ok(events) => events.iter().for_each(print_event),
112 Err(err) => {
113 eprintln!("timeout polling failed: {err}");
114 break;
115 }
116 }
117 }
118
119 match client.close().await {
120 Ok(events) => events.iter().for_each(print_event),
121 Err(err) => eprintln!("close failed: {err}"),
122 }
123}Sourcepub fn poll_timeouts(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub fn poll_timeouts(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Poll protocol timeouts using a newly captured monotonic timestamp.
Examples found in repository?
42async fn run() {
43 let config = ClientConfig {
44 server_addr: "127.0.0.1:2112".to_owned(),
45 duration: Some(Duration::from_secs(2)),
46 interval: Duration::from_millis(200),
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 open_timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 };
52
53 let mut client = match AsyncClient::connect(config).await {
54 Ok(client) => client,
55 Err(err) => {
56 eprintln!("failed to prepare client socket: {err}");
57 return;
58 }
59 };
60
61 match client.open().await {
62 Ok(outcome) => println!("session opened: {outcome:?}"),
63 Err(err) => {
64 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65 return;
66 }
67 }
68
69 while !client.is_run_complete() {
70 // Bound the receive wait by the next send/timeout deadline, rather
71 // than awaiting recv() unconditionally: a lost or rate-limited reply
72 // must not stall pacing or timeout classification indefinitely.
73 let wake_at = client
74 .next_send_deadline()
75 .into_iter()
76 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
77 .min()
78 .expect("the fixed poll-interval deadline is always present");
79
80 let recv_result = tokio::select! {
81 events = client.recv() => Some(events),
82 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
83 };
84 if let Some(result) = recv_result {
85 match result {
86 Ok(events) => events.iter().for_each(print_event),
87 Err(err) => {
88 eprintln!("receive failed: {err}");
89 break;
90 }
91 }
92 }
93
94 if client
95 .next_send_deadline()
96 .is_some_and(|send_at| Instant::now() >= send_at)
97 {
98 // A failed send retains its prepared probe without advancing the
99 // schedule, so a persistent error (e.g. the interface going
100 // down) would otherwise make this loop retry forever instead of
101 // reporting it.
102 match client.send_probe().await {
103 Ok(events) => events.iter().for_each(print_event),
104 Err(err) => {
105 eprintln!("send failed: {err}");
106 break;
107 }
108 }
109 }
110 match client.poll_timeouts() {
111 Ok(events) => events.iter().for_each(print_event),
112 Err(err) => {
113 eprintln!("timeout polling failed: {err}");
114 break;
115 }
116 }
117 }
118
119 match client.close().await {
120 Ok(events) => events.iter().for_each(print_event),
121 Err(err) => eprintln!("close failed: {err}"),
122 }
123}Sourcepub fn poll_timeouts_at(
&mut self,
now: Instant,
) -> Result<Vec<ClientEvent>, ClientError>
pub fn poll_timeouts_at( &mut self, now: Instant, ) -> Result<Vec<ClientEvent>, ClientError>
Poll protocol timeouts using the caller’s monotonic timestamp.
Sourcepub async fn close(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub async fn close(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Send the retained close packet and commit local close exactly once.
DSCP is cleared only after write readiness and immediately before the
nonblocking send attempt. A WouldBlock result restores it before the
next await, so dropping this future at a suspension point cannot leave
an otherwise-open session with cleared DSCP.
Examples found in repository?
42async fn run() {
43 let config = ClientConfig {
44 server_addr: "127.0.0.1:2112".to_owned(),
45 duration: Some(Duration::from_secs(2)),
46 interval: Duration::from_millis(200),
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 open_timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 };
52
53 let mut client = match AsyncClient::connect(config).await {
54 Ok(client) => client,
55 Err(err) => {
56 eprintln!("failed to prepare client socket: {err}");
57 return;
58 }
59 };
60
61 match client.open().await {
62 Ok(outcome) => println!("session opened: {outcome:?}"),
63 Err(err) => {
64 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65 return;
66 }
67 }
68
69 while !client.is_run_complete() {
70 // Bound the receive wait by the next send/timeout deadline, rather
71 // than awaiting recv() unconditionally: a lost or rate-limited reply
72 // must not stall pacing or timeout classification indefinitely.
73 let wake_at = client
74 .next_send_deadline()
75 .into_iter()
76 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
77 .min()
78 .expect("the fixed poll-interval deadline is always present");
79
80 let recv_result = tokio::select! {
81 events = client.recv() => Some(events),
82 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
83 };
84 if let Some(result) = recv_result {
85 match result {
86 Ok(events) => events.iter().for_each(print_event),
87 Err(err) => {
88 eprintln!("receive failed: {err}");
89 break;
90 }
91 }
92 }
93
94 if client
95 .next_send_deadline()
96 .is_some_and(|send_at| Instant::now() >= send_at)
97 {
98 // A failed send retains its prepared probe without advancing the
99 // schedule, so a persistent error (e.g. the interface going
100 // down) would otherwise make this loop retry forever instead of
101 // reporting it.
102 match client.send_probe().await {
103 Ok(events) => events.iter().for_each(print_event),
104 Err(err) => {
105 eprintln!("send failed: {err}");
106 break;
107 }
108 }
109 }
110 match client.poll_timeouts() {
111 Ok(events) => events.iter().for_each(print_event),
112 Err(err) => {
113 eprintln!("timeout polling failed: {err}");
114 break;
115 }
116 }
117 }
118
119 match client.close().await {
120 Ok(events) => events.iter().for_each(print_event),
121 Err(err) => eprintln!("close failed: {err}"),
122 }
123}Sourcepub fn next_send_deadline(&self) -> Option<Instant>
pub fn next_send_deadline(&self) -> Option<Instant>
Return the next scheduled probe deadline.
Examples found in repository?
42async fn run() {
43 let config = ClientConfig {
44 server_addr: "127.0.0.1:2112".to_owned(),
45 duration: Some(Duration::from_secs(2)),
46 interval: Duration::from_millis(200),
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 open_timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 };
52
53 let mut client = match AsyncClient::connect(config).await {
54 Ok(client) => client,
55 Err(err) => {
56 eprintln!("failed to prepare client socket: {err}");
57 return;
58 }
59 };
60
61 match client.open().await {
62 Ok(outcome) => println!("session opened: {outcome:?}"),
63 Err(err) => {
64 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65 return;
66 }
67 }
68
69 while !client.is_run_complete() {
70 // Bound the receive wait by the next send/timeout deadline, rather
71 // than awaiting recv() unconditionally: a lost or rate-limited reply
72 // must not stall pacing or timeout classification indefinitely.
73 let wake_at = client
74 .next_send_deadline()
75 .into_iter()
76 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
77 .min()
78 .expect("the fixed poll-interval deadline is always present");
79
80 let recv_result = tokio::select! {
81 events = client.recv() => Some(events),
82 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
83 };
84 if let Some(result) = recv_result {
85 match result {
86 Ok(events) => events.iter().for_each(print_event),
87 Err(err) => {
88 eprintln!("receive failed: {err}");
89 break;
90 }
91 }
92 }
93
94 if client
95 .next_send_deadline()
96 .is_some_and(|send_at| Instant::now() >= send_at)
97 {
98 // A failed send retains its prepared probe without advancing the
99 // schedule, so a persistent error (e.g. the interface going
100 // down) would otherwise make this loop retry forever instead of
101 // reporting it.
102 match client.send_probe().await {
103 Ok(events) => events.iter().for_each(print_event),
104 Err(err) => {
105 eprintln!("send failed: {err}");
106 break;
107 }
108 }
109 }
110 match client.poll_timeouts() {
111 Ok(events) => events.iter().for_each(print_event),
112 Err(err) => {
113 eprintln!("timeout polling failed: {err}");
114 break;
115 }
116 }
117 }
118
119 match client.close().await {
120 Ok(events) => events.iter().for_each(print_event),
121 Err(err) => eprintln!("close failed: {err}"),
122 }
123}Sourcepub fn probe_timeout(&self) -> Duration
pub fn probe_timeout(&self) -> Duration
Return the configured local probe timeout.
Sourcepub fn is_run_complete(&self) -> bool
pub fn is_run_complete(&self) -> bool
Return whether the current run has completed.
Examples found in repository?
42async fn run() {
43 let config = ClientConfig {
44 server_addr: "127.0.0.1:2112".to_owned(),
45 duration: Some(Duration::from_secs(2)),
46 interval: Duration::from_millis(200),
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 open_timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 };
52
53 let mut client = match AsyncClient::connect(config).await {
54 Ok(client) => client,
55 Err(err) => {
56 eprintln!("failed to prepare client socket: {err}");
57 return;
58 }
59 };
60
61 match client.open().await {
62 Ok(outcome) => println!("session opened: {outcome:?}"),
63 Err(err) => {
64 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65 return;
66 }
67 }
68
69 while !client.is_run_complete() {
70 // Bound the receive wait by the next send/timeout deadline, rather
71 // than awaiting recv() unconditionally: a lost or rate-limited reply
72 // must not stall pacing or timeout classification indefinitely.
73 let wake_at = client
74 .next_send_deadline()
75 .into_iter()
76 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
77 .min()
78 .expect("the fixed poll-interval deadline is always present");
79
80 let recv_result = tokio::select! {
81 events = client.recv() => Some(events),
82 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
83 };
84 if let Some(result) = recv_result {
85 match result {
86 Ok(events) => events.iter().for_each(print_event),
87 Err(err) => {
88 eprintln!("receive failed: {err}");
89 break;
90 }
91 }
92 }
93
94 if client
95 .next_send_deadline()
96 .is_some_and(|send_at| Instant::now() >= send_at)
97 {
98 // A failed send retains its prepared probe without advancing the
99 // schedule, so a persistent error (e.g. the interface going
100 // down) would otherwise make this loop retry forever instead of
101 // reporting it.
102 match client.send_probe().await {
103 Ok(events) => events.iter().for_each(print_event),
104 Err(err) => {
105 eprintln!("send failed: {err}");
106 break;
107 }
108 }
109 }
110 match client.poll_timeouts() {
111 Ok(events) => events.iter().for_each(print_event),
112 Err(err) => {
113 eprintln!("timeout polling failed: {err}");
114 break;
115 }
116 }
117 }
118
119 match client.close().await {
120 Ok(events) => events.iter().for_each(print_event),
121 Err(err) => eprintln!("close failed: {err}"),
122 }
123}Sourcepub fn is_peer_closed(&self) -> bool
pub fn is_peer_closed(&self) -> bool
Return whether an authenticated peer close ended the session.