ironrdp-async crate, with runtime-specific implementations for Tokio and Futures. This guide covers async patterns, performance optimization, and best practices.
Async vs Blocking
When to Use Async
✅ Use async I/O when:- Handling multiple concurrent connections
- Building servers that need to scale
- Integrating with existing async codebases
- Requiring non-blocking I/O for responsiveness
When to Use Blocking
✅ Use blocking I/O when:- Building simple single-connection clients
- Prototyping or testing
- Working in environments without async runtime
- Simplicity is more important than concurrency
Architecture
IronRDP’s async architecture uses these crates:ironrdp-async- Runtime-agnostic async abstractionsironrdp-tokio- Tokio-specific implementationsironrdp-futures- Futures-specific implementations
Async Client Example
Here’s a complete async client using Tokio:[dependencies]
ironrdp = "0.1"
ironrdp-async = "0.1"
ironrdp-tokio = "0.1"
tokio = { version = "1", features = ["full"] }
tokio-rustls = "0.26"
anyhow = "1.0"
tracing = "0.1"
use ironrdp::connector::{self, Credentials, ClientConnector};
use ironrdp_async::{connect_begin, mark_as_upgraded, connect_finalize};
use ironrdp_tokio::TokioFramed;
use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = connector::Config {
credentials: Credentials::UsernamePassword {
username: "user".to_owned(),
password: "pass".to_owned(),
},
domain: None,
enable_tls: false,
enable_credssp: true,
// ... other config fields
};
let tcp_stream = TcpStream::connect(("server.example.com", 3389)).await?;
let client_addr = tcp_stream.local_addr()?;
let mut framed = TokioFramed::new(tcp_stream);
let mut connector = ClientConnector::new(config, client_addr);
// Begin connection (async)
let should_upgrade = connect_begin(&mut framed, &mut connector).await?;
// TLS upgrade (see next step)
// ...
Ok(())
}
use tokio_rustls::{TlsConnector, rustls};
use std::sync::Arc;
async fn tls_upgrade(
stream: TcpStream,
server_name: &str,
) -> anyhow::Result<tokio_rustls::client::TlsStream<TcpStream>> {
let mut config = rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(/* ... */)
.with_no_client_auth();
config.resumption = rustls::client::Resumption::disabled();
let connector = TlsConnector::from(Arc::new(config));
let domain = rustls::pki_types::ServerName::try_from(server_name)?;
let tls_stream = connector.connect(domain.to_owned(), stream).await?;
Ok(tls_stream)
}
// Usage:
let initial_stream = framed.into_inner_no_leftover();
let tls_stream = tls_upgrade(initial_stream, "server.example.com").await?;
let upgraded = mark_as_upgraded(should_upgrade, &mut connector);
let mut upgraded_framed = TokioFramed::new(tls_stream);
use sspi::network_client::reqwest_network_client::ReqwestNetworkClient;
let mut network_client = ReqwestNetworkClient;
let connection_result = connect_finalize(
upgraded,
connector,
&mut upgraded_framed,
&mut network_client,
"server.example.com".into(),
server_public_key,
None,
).await?;
println!("Connected! Desktop size: {}x{}",
connection_result.desktop_size.width,
connection_result.desktop_size.height);
use ironrdp::session::{ActiveStage, ActiveStageOutput};
use ironrdp::session::image::DecodedImage;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let mut image = DecodedImage::new(
ironrdp_graphics::image_processing::PixelFormat::RgbA32,
connection_result.desktop_size.width,
connection_result.desktop_size.height,
);
let mut active_stage = ActiveStage::new(connection_result);
loop {
let (action, payload) = upgraded_framed.read_pdu().await?;
let outputs = active_stage.process(&mut image, action, &payload)?;
for output in outputs {
match output {
ActiveStageOutput::ResponseFrame(frame) => {
upgraded_framed.write_all(&frame).await?;
}
ActiveStageOutput::GraphicsUpdate => {
// Trigger async redraw
tokio::spawn(async move {
render_image(&image).await;
});
}
ActiveStageOutput::Terminate(_) => return Ok(()),
_ => {}
}
}
}
Async Server Example
Theironrdp-server crate is built on async/await:
Concurrency Patterns
Multiple Concurrent Connections
Async Channel Communication
Timeout Handling
Graceful Shutdown
Performance Optimization
Buffering Strategy
Batch Processing
Connection Pooling
Error Handling
Retry Logic
Async Error Propagation
Runtime Selection
Using Tokio
Using async-std (via Futures)
Testing Async Code
Best Practices
- Use
tokio::spawnfor independent tasks - Don’t block the async runtime - Avoid blocking operations - Use async alternatives or
spawn_blocking - Handle backpressure - Use bounded channels and flow control
- Set timeouts - Always timeout long-running operations
- Graceful shutdown - Clean up resources properly
- Monitor task health - Use supervision strategies for critical tasks
Next Steps
- Building a Client - Build async clients
- Building a Server - Understand the async server
- Virtual Channels - Async channel processing

