Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No simple example for concurrent connections #322

Open
George3d6 opened this issue Sep 15, 2018 · 4 comments
Open

No simple example for concurrent connections #322

George3d6 opened this issue Sep 15, 2018 · 4 comments
Labels

Comments

@George3d6
Copy link

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
Copy link
Author

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

@carllerche carllerche transferred this issue from tokio-rs/tokio Jun 25, 2019
@ultrasaurus
Copy link
Contributor

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!

@hawkw
Copy link
Member

hawkw commented Feb 15, 2020

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!");
}

@ultrasaurus ultrasaurus added the example Idea for an example to add to the docs label Feb 15, 2020
@ultrasaurus
Copy link
Contributor

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

jefflembeck pushed a commit to jefflembeck/website that referenced this issue Apr 3, 2020
@Darksonn Darksonn mentioned this issue Jul 23, 2020
19 tasks
@Darksonn Darksonn added help wanted Help is wanted page-or-section and removed example Idea for an example to add to the docs labels Jul 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants
@ultrasaurus @Darksonn @hawkw @George3d6 and others