【问题标题】:How to set up Tokio as a multi-threaded UDP server?如何将 Tokio 设置为多线程 UDP 服务器?
【发布时间】:2020-05-13 15:45:16
【问题描述】:

到目前为止,我发现的唯一示例是 Tokio 存储库中的单线程示例 Echo UDP。如何使用 Tokio 启动一个循环,该循环将产生一个新线程来处理新的 UDP 连接。

【问题讨论】:

  • 更改您的问题以使现有答案无效是违反 Stack Overflow 礼仪的。如果不出意外,它会让现有的回答者,试图帮助你的人显得愚蠢和无能。
  • 问题没有改,只是改写的更清楚了。我知道在任何论坛中更改问题都是不礼貌的。老实说,虽然我的问题本来可以更清楚地表达出来,但正是你的回答让我首先尝试更仔细地重新表达它......

标签: sockets rust udp rust-tokio


【解决方案1】:

original version of the question的答案

如何设置 Tokio 0.2 以“监听”UDP 数据?

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());
    }
}

(Tokio 1.4 的代码实际上是相同的,只需删除 mut 限定符。)

在一个窗口中:

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

另一个:

% nc -u 127.0.0.1 9999
alpha
beta
gamma

current version of the question 的答案

如何将 Tokio 设置为多线程 UDP 服务器?

上面的代码多线程的; Tokio is multithreaded by default。您可能正在寻求创建并发(可能是并行)工作;这可以通过生成任务来完成:

【讨论】:

  • 这是处理传入 UDP 数据的一个很好的简单示例,但我正在寻找的是多线程解决方案。我试图改进我的问题以使其更清楚。
  • @allsey87 这多线程的; Tokio 是multithreaded by default
  • 好的,我想我现在开始明白这一点了。我是否正确地说,一次只有一个线程(可能来自 Tokio 的线程池)与来自 UdpSocket 的(读取/写入数据)交互?
  • @allsey87 是的;在 general 中,这就是您想要的,否则您需要执行某种锁定以确保数据不会被破坏。对于像套接字这样的系统东西,OS does that internally 所以你也可以选择同时使用它们。
  • 将此标记为正确,因为它最接近我的问题的解决方案。我有几个误解,导致我认为我希望为每个将数据发送到我的服务器的客户端都有一个线程。我现在可以看到,由于多种原因,这种逻辑存在很大缺陷。
【解决方案2】:

要在 tokio 中创建可调度单元,您应该使用 tokio::task::spawn。如果底层运行时是多线程的,那么这些单元将由多个线程完成。

您可以通过在示例中添加几行来了解它是如何工作的

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());
    }

【讨论】:

  • 当您说“底层运行时”时,您是指执行者吗? Tokio 的默认执行器不总是多线程执行器吗?
  • 另外,在你有 let amt = socket.send_to(&amp;buf[..size], &amp;peer).await?; 的循环中,我是否正确理解,在返回的未来运行完成之前,该服务器将无法执行任何其他操作?
  • @allsey87 是的,tokio 的默认执行器是多线程的。是的,在示例中,主线程继续运行,并且处理程序在未来完成之前不能做任何其他事情。但这不是必须的,您可以在任何地方以相同的方式生成另一个任务。例如,在读取 udp 数据包后,您可以生成另一个任务来处理它/发送响应。
  • @boran 不,你不能从另一个任务/线程发送响应,因为你需要向它发送套接字,这将在当前循环中丢失
猜你喜欢
  • 1970-01-01
  • 2014-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-11
  • 1970-01-01
  • 2015-01-09
相关资源
最近更新 更多