Search code examples
rustrust-tokio

Can tokio::select allow defining arbitrary numbre of branches


I am writing an echo server that is able to listen on multiple ports. My working server below relies on select to accept connections from 2 different listeners

However, instead of defining the listeners as individual variables, is it possible to define select branches based on a Vec<TcpListener> ?

use tokio::{io, net, select, spawn};

#[tokio::main]
async fn main() {
    let listener1 = net::TcpListener::bind("127.0.0.1:8001").await.unwrap();
    let listener2 = net::TcpListener::bind("127.0.0.1:8002").await.unwrap();

    loop {
        let (conn, _) = select! {
            v = listener1.accept() => v.unwrap(),
            v = listener2.accept() => v.unwrap(),
        };
        spawn(handle(conn));
    }
}

async fn handle(mut conn: net::TcpStream) {
    let (mut read, mut write) = conn.split();
    io::copy(&mut read, &mut write).await.unwrap();
}


Solution

  • While futures::future::select_all() works, it is not very elegant (IMHO) and it creates an allocation for each round. A better solution is to use streams (note this also allocates on each round, but this allocates much less):

    use tokio::{io, net, spawn};
    use tokio_stream::wrappers::TcpListenerStream;
    use futures::stream::{StreamExt, SelectAll};
    
    #[tokio::main]
    async fn main() {
        let mut listeners = SelectAll::new();
        listeners.push(TcpListenerStream::new(net::TcpListener::bind("127.0.0.1:8001").await.unwrap()));
        listeners.push(TcpListenerStream::new(net::TcpListener::bind("127.0.0.1:8002").await.unwrap()));
    
        while let Some(conn) = listeners.next().await {
            let conn = conn.unwrap();
            spawn(handle(conn));
        }
    }