Acquiring a socket

Accepting TCP connections with Tokio is much like doing so with std::net, except that it works asynchronously. In particular, tokio::net contains a TcpListener with an API similar to the std::net version:


# #![allow(unused_variables)]
#fn main() {
type AsyncIoResult<T> = AsyncResult<T, io::Error>;

impl TcpListener {
    fn bind(addr: &SocketAddr) -> io::Result<TcpListener>;
    fn accept(&mut self) -> AsyncIoResult<(TcpStream, SocketAddr)>;
}
#}

Just like the occurrence of Result in a signature tells you that a function may fail with an error, the occurrence of Async or AsyncResult tells you that the function is intended to be used within the async task system. Thus, looking at the two functions above, we can see that bind is a a standard synchronous function, while accept is an asynchronous method.

To quickly see these APIs in action, let's build a future that will accept connections asynchronously, record the peer address, and then close the connection:


# #![allow(unused_variables)]
#fn main() {
use tokio::net::TcpListener;

struct LogAndDrop {
    listener: TcpListener,
}

impl LogAndDrop {
    fn new(addr: &SocketAddr) -> io::Result<LogAndDrop> {
        LogAndDrop {
            listener: TcpListener::bind(addr)?
        }
    }
}

impl Future for LogAndDrop {
    type Item = ();
    type Error = io::Error;

    fn complete(&mut self) -> AsyncIoResult<()> {
        loop {
            match self.listener.accept(wake) {
                Ok(Async::Done((_, peer))) => {
                    println!("Connected to peer {:?}", peer);
                }
                Ok(Async::WillWake) => {
                    return Ok(Async::WillWake);
                }
                Err(e) => {
                    println!("Error: {:?}; shutting down", e);
                    return Err(e);
                }
            }
        }
    }
}
#}

Note that, in the case that we succeed in accepting a connection, after logging it we immediately loop and try to take another. This is typical for async tasks: the task code does as much work as it possibly can, stopping only when encountering an obstruction (such as the listener returning WillWake), at which point it will be descheduled and woken up later, when the obstruction has been cleared.