Data Transfer
The previous chapter explained how to set up an Endpoint and then get access to a Connection. This chapter continues with the subject of sending data over this connection.
Multiplexing
Multiplexing is the act of combining data from multiple streams into a single stream. This can have a significant positive effect on the performance of the application. With QUIC, the programmer is in full control over the stream allocation.
Stream Types
QUIC provides support for both stream and message-based communication. Streams and messages can be initiated both on the client and server.
Type | Description | Reference |
---|---|---|
Bidirectional Stream | two way stream communication. | see open_bi |
Unidirectional Stream | one way stream communication. | see open_uni |
Unreliable Messaging (extension) | message based unreliable communication. | see send_datagram |
How to Use
New streams can be created with Connection's open_bi() and open_uni() methods.
Bidirectional Streams
With bidirectional streams, data can be sent in both directions. For example, from the connection initiator to the peer and the other way around.
open bidirectional stream
#![allow(unused)] fn main() { async fn open_bidirectional_stream(connection: Connection) -> anyhow::Result<()> { let (mut send, recv) = connection .open_bi() .await?; send.write_all(b"test").await?; send.finish().await?; let received = recv.read_to_end(10).await?; Ok(()) } }
iterate incoming bidirectional stream(s)
#![allow(unused)] fn main() { async fn receive_bidirectional_stream(connection: Connection) -> anyhow::Result<()> { while let Ok((mut send, recv)) = connection.accept_bi().await { // Because it is a bidirectional stream, we can both send and receive. println!("request: {:?}", recv.read_to_end(50).await?); send.write_all(b"response").await?; send.finish().await?; } Ok(()) } }
Unidirectional Streams
With unidirectional streams, you can carry data only in one direction: from the initiator of the stream to its peer. It is possible to get reliability without ordering (so no head-of-line blocking) by opening a new stream for each packet.
open unidirectional stream
#![allow(unused)] fn main() { async fn open_unidirectional_stream(connection: Connection)-> anyhow::Result<()> { let mut send = connection .open_uni() .await?; send.write_all(b"test").await?; send.finish().await?; Ok(()) } }
iterating incoming unidirectional stream(s)
#![allow(unused)] fn main() { async fn receive_unidirectional_stream(connection: Connection) -> anyhow::Result<()> { while let Ok(recv) = connection.accept_uni().await { // Because it is a unidirectional stream, we can only receive not send back. println!("{:?}", recv.read_to_end(50).await?); } Ok(()) } }
Unreliable Messaging
With unreliable messaging, you can transfer data without reliability. This could be useful if data arrival isn't essential or when high throughput is important.
send datagram
#![allow(unused)] fn main() { async fn send_unreliable(connection: Connection)-> anyhow::Result<()> { connection .send_datagram(b"test".into()) .await?; Ok(()) } }
iterating datagram stream(s)
#![allow(unused)] fn main() { async fn receive_datagram(connection: Connection) -> anyhow::Result<()> { while let Ok(received_bytes) = connection.read_datagram().await { // Because it is a unidirectional stream, we can only receive not send back. println!("request: {:?}", received); } Ok(()) } }