website icon indicating copy to clipboard operation
website copied to clipboard

No simple example for concurrent connections

Open George3d6 opened this issue 7 years ago • 4 comments

Currently there are no simple examples that show an easy "client side" task such as concurrently "sending" two tcp messages.

Essentially all the trivial examples show all the asynchronous tasks being crammed inside the for_each function of a TcpListener.

Would you consider adding some examples of the "correct" way of doing a simple tasks such as launching 2 concurrent connections over tcp and waiting until both of them send & receive a message ?

George3d6 avatar Sep 15 '18 21:09 George3d6

Note, just to give an example of what I'm referring to, something like this:

extern crate tokio;
extern crate futures;

use futures::future;
use tokio::{net::TcpStream, prelude::Future};
use std::net::SocketAddr;

fn send(addr: SocketAddr, message: String) {
    let task = TcpStream::connect(&addr)
    .and_then(|stream| tokio::io::write_all(stream, message))
    .map_err(|e| println!("Error: {}", e))
    .map(|_| ());

    tokio::spawn(task);
}

fn main() {
    tokio::run(future::lazy(|| {
        send("8.8.8.8:1234".parse().unwrap(), String::from("46"));
        send("1.1.1.1:4321".parse().unwrap(), String::from("3"));
        Ok(())
    }));
}

Though ideally w/o the use of the futures crate explicitly, if possible.

Instead of something more complex, like this: https://github.com/tokio-rs/tokio/blob/master/examples/manual-runtime.rs

George3d6 avatar Sep 15 '18 21:09 George3d6

Here's an approach that works for me. I added lots of println's to illustrate...

use tokio::prelude::*;
use tokio::net::TcpStream;
use std::net::SocketAddr;

async fn send(addr: SocketAddr, message: String) {
  println!("send: {} to {}", message, addr);
  let result = TcpStream::connect(&addr).await;
  if let Ok(mut tcp) = result {
    let write_result = tcp.write_all(message.as_bytes()).await;
    println!("write_result = {:?} for {} to {}", write_result, message, addr);
  } else {
    println!("connection failed for {} to {}", message, addr);
  }
}



#[tokio::main]
async fn main() {
  pretty_env_logger::init();
  let addr1:SocketAddr  = "127.0.0.1:1935".parse().unwrap();
  let addr2:SocketAddr  = "127.0.0.1:34254".parse().unwrap();

  let one = send(addr1, "Hello!".to_string());
  let two = send(addr2, "GET".to_string());
  let tasks = vec![one, two];

  futures::future::join_all(tasks).await;
  println!("done!");
}

output (on my machine I'm running a server on 1935 but not on 34254:

send: Hello! to 127.0.0.1:1935
send: GET to 127.0.0.1:34254
write_result = Ok(()) for Hello! to 127.0.0.1:1935
connection failed for GET to 127.0.0.1:34254
done!

ultrasaurus avatar Feb 15 '20 00:02 ultrasaurus

If the intention is to run both connections in parallel, you'd probably want to spawn the send futures, as in

#[tokio::main]
async fn main() {
  pretty_env_logger::init();
  let addr1:SocketAddr  = "127.0.0.1:1935".parse().unwrap();
  let addr2:SocketAddr  = "127.0.0.1:34254".parse().unwrap();

  let one = tokio::spawn(send(addr1, "Hello!".to_string()));
  let two = tokio::spawn(send(addr2, "GET".to_string()));
  let tasks = vec![one, two];

  futures::future::join_all(tasks).await;
  println!("done!");
}

also, i think this example would be a bit neater (and wouldn't require allocating a Vec, or using code from the futures crate) if we used tokio's join macro instead:


#[tokio::main]
async fn main() {
  pretty_env_logger::init();
  let addr1:SocketAddr  = "127.0.0.1:1935".parse().unwrap();
  let addr2:SocketAddr  = "127.0.0.1:34254".parse().unwrap();

  let one = tokio::spawn(send(addr1, "Hello!".to_string()));
  let two = tokio::spawn(send(addr2, "GET".to_string()));

  tokio::join!(one, two);
  println!("done!");
}

hawkw avatar Feb 15 '20 00:02 hawkw

great examples @hawkw ! I had forgotten about the tokio::join! macro.

It would be great around this kind of an example to talk about threads vs tasks. I believe #[tokio::main] uses threads by default who has pros/cons -- for clients threads can be pretty heavy weight and would be good to brainstorm some real-world problems that might illustrate different trade-offs.

Some ideas

  1. Multiple web requests (especially if they are to different servers, where running them in parallel is clearly beneficial)
  2. Multiple writes on a single socket, where response from first may arrive independently of second, so don't want to block on first send/receive (which is what I thought @George3d6 was describing originally

ultrasaurus avatar Feb 15 '20 01:02 ultrasaurus