1

So far the only example I have found is the single threaded example Echo UDP from the Tokio repository. How does one start a loop with Tokio that will spawn a new thread to handle new UDP connections.

mallwright
  • 1,670
  • 14
  • 31
  • It is against Stack Overflow etiquette to change your question such that it invalidates existing answers. If nothing else, it makes existing answerers, *people who are trying to help you*, look stupid and inept. – Shepmaster May 14 '20 at 12:47
  • The question has not been changed, just rephrased more clearly. I am aware that changing the question is bad etiquette in any forum. To be honest, although my question could have been phrased more clearly, it was your answer which lead me to try and rephrase it more carefully in the first place... – mallwright May 14 '20 at 13:58

2 Answers2

2

Answer for the original version of the question

How does one set up Tokio 0.2 to “listen” for UDP data?

use tokio::net::UdpSocket; // "0.2.20", features = ["full"]

type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;

#[tokio::main]
async fn main() -> Result<()> {
    let mut socket = UdpSocket::bind("127.0.0.1:9999").await?;

    loop {
        let mut data = [0; 1024];
        let valid_bytes = socket.recv(&mut data).await?;
        let data = &data[..valid_bytes];

        eprintln!("Read {} bytes", data.len());
    }
}

(The code is effectively identical for Tokio 1.4, just remove the mut qualifier.)

In one window:

% cargo run
Read 6 bytes
Read 5 bytes
Read 6 bytes

In another:

% nc -u 127.0.0.1 9999
alpha
beta
gamma

Answer for the current version of the question

How to set up Tokio as a multi-threaded UDP server?

The code above is multi threaded; Tokio is multithreaded by default. You may be looking to create concurrent (and possibly parallel) work; That can be done via spawning a task:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    This is a nice simple example of handling incoming UDP data, but what I am looking for is a multi-threaded solution. I have tried to improve my question to make this clearer. – mallwright May 14 '20 at 07:19
  • 1
    @allsey87 This **is** multi threaded; Tokio is [multithreaded by default](https://stackoverflow.com/a/61753175/155423). – Shepmaster May 14 '20 at 12:40
  • Ok, I think I am starting to understand this now. Am I correct in saying though that only one thread (presumably from Tokio's threadpool) at a time is interacting with (reading/writing data) from the `UdpSocket`? – mallwright May 14 '20 at 14:08
  • @allsey87 yes; in *general* that’s what you want as otherwise you need to perform some kind of locking to ensure that data isn’t mangled. For system things like sockets, the [OS does that internally](https://stackoverflow.com/q/31503488/155423) so you can also choose to use them concurrently. – Shepmaster May 14 '20 at 14:14
  • Marking this correct since it is the closest to a solution to my problem. I had several misconceptions that lead me to thinking that I would want to have a thread for each client that was sending data to my server. I can see now that this logic is pretty flawed for a number of reasons. – mallwright May 14 '20 at 14:36
2

To create a schedulable unit in tokio you should use tokio::task::spawn. If underlying runtime is multithreaded then these units will be completed by multiple threads.

You can see how it works by adding a couple of lines to the example

fn main() {
...
    let jh = tokio::task::spawn(server.run());
    println!("udp server started {:?}", std::thread::current().id());
    jh.await?;
...
}
fn run
... 
   loop {
        if let Some((size, peer)) = to_send {
            let amt = socket.send_to(&buf[..size], &peer).await?;
            println!("eched back {:?}", std::thread::current().id());
        }

        to_send = Some(socket.recv_from(&mut buf).await?);
        println!("read some stuff {:?}", std::thread::current().id());
    }
ekim boran
  • 1,809
  • 12
  • 22
  • When you say "underlying runtime" do you mean the executor? Isn't the default executor for Tokio always a multi-threaded executor? – mallwright May 14 '20 at 11:47
  • 1
    Also, in your loop where you have `let amt = socket.send_to(&buf[..size], &peer).await?;` am I correct in understanding that this server will not be able to do anything else until the returned future has been run to completion? – mallwright May 14 '20 at 11:50
  • 1
    @allsey87 Yes tokio's default executer is multithreaded. Yes in the example, main thread continues to run and the handler cannot do anything else until the future completes. But it does not have to be, you can spawn another task same way anywhere you want. For example after reading a udp packet you can spawn another task to process it / send response. – ekim boran May 14 '20 at 12:24
  • @boran no you cannot send response from another task/thread, as you would need to send socket to it, which will be lost for current loop – dark_ruby Jun 11 '21 at 18:40