If you want to have two completely independent streams of data on the socket, you can use the split()
method on the TcpStream
in the current version of Tokio:
let connection = TcpStream::connect(&address);
connection.and_then(|socket| {
let (rx, tx) = socket.split();
//Independently use tx/rx for sending/receiving
return Ok(());
});
After the split, you can use rx
(the receiving half) and tx
(the sending half) independently. Here is a small example that treats sending and receiving as completely independent. The sender-half simply periodically sends the same message, whereas the receiving half just prints all incomming data:
extern crate futures;
extern crate tokio;
use self::futures::{Future, Poll, Stream};
use self::tokio::net::TcpStream;
use tokio::io::{AsyncRead, AsyncWrite, Error, ReadHalf};
use tokio::prelude::*;
use tokio::timer::Interval;
//Receiver struct that implements the future trait
//this exclusively handles incomming data and prints it to stdout
struct Receiver {
rx: ReadHalf<TcpStream>, //receiving half of the socket stream
}
impl Future for Receiver {
type Item = ();
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut buffer = vec![0u8; 1000]; //reserve 1000 bytes in the receive buffer
//get all data that is available to us at the moment...
while let Async::Ready(num_bytes_read) = self.rx.poll_read(&mut buffer)? {
if num_bytes_read == 0 {
return Ok(Async::Ready(()));
} //socket closed
print!("{}", String::from_utf8_lossy(&buffer[..num_bytes_read]));
}
return Ok(Async::NotReady);
}
}
fn main() {
let address = "127.0.0.1:2323".parse().expect("Unable to parse address");
let connection = TcpStream::connect(&address);
//wait for the connection to be established
let client = connection
.and_then(|socket| {
//split the successfully connected socket in half (receive / send)
let (rx, mut tx) = socket.split();
//set up a simple sender, that periodically (1sec) sends the same message
let sender = Interval::new_interval(std::time::Duration::from_millis(1000))
.for_each(move |_| {
//this lambda is invoked once per passed second
tx.poll_write(&vec![82, 117, 115, 116, 10]).map_err(|_| {
//shut down the timer if an error occured (e.g. socket was closed)
tokio::timer::Error::shutdown()
})?;
return Ok(());
}).map_err(|e| println!("{}", e));
//start the sender
tokio::spawn(sender);
//start the receiver
let receiver = Receiver { rx };
tokio::spawn(receiver.map_err(|e| println!("{}", e)));
return Ok(());
}).map_err(|e| println!("{}", e));
tokio::run(client);
}
For some applications, this is enough. However, often you will have a defined protocol / format on the connection. HTTP connections, for example, always consist of requests and responses, each of which consists of a header and the body. Instead of directly working on the byte level, Tokio offers the traits Encoder
and Decoder
you fit onto a socket, which decodes your protocol and directly gives you the entities you want to work with. For an example you can either look at the very basic HTTP implementation or the line-based codec.
It gets a bit more complicated when an incoming message triggers an outgoing message. For the simplest case (every incoming message leads to exactly one outgoing message) you can have a look at this official request / response example.