I/O Overview

The Rust standard library provides support for networking and I/O, suchas TCP connections, UDP sockets, reading from and writing to files, etc.However, those operations are all synchronous, or blocking, meaningthat when you call them, the current thread may stop executing and go tosleep until it is unblocked. For example, the read method instd::io::Read will block until there is data to read. In the worldof futures, that behavior is unfortunate, since we would like tocontinue executing other futures we may have while waiting for the I/Oto complete.

To enable this, Tokio provides non-blocking versions of many standardlibrary I/O resources, such as file operations and TCP, UDP, andUnix sockets. They return futures for long-running operations (likeaccepting a new TCP connection), and implement non-blocking variants ofstd::io::Read and std::io::Write called AsyncRead andAsyncWrite.

Non-blocking reads and writes do not block if, for example, there is nomore data available. Instead, they return immediately with aWouldBlock error, along with a guarantee (like Future::poll) thatthey have arranged for the current task to be woken up when they canlater make progress, such as when a network packet arrives.

By using the non-blocking Tokio I/O types, a future that performs I/Ono longer blocks execution of other futures if the I/O they wish toperform cannot be performed immediately. Instead, it simply returnsNotReady, and relies on a task notification to cause poll to becalled again, and which point its I/O should succeed without blocking.

Behind the scenes, Tokio uses mio and tokio-fs to keep track ofthe status of the various I/O resources that different futures arewaiting for, and is notified by the operating system whenever the statusof any of them change.

An example server

To get a sense of how this fits together, consider this echoserver implementation:

  1. # extern crate tokio;
  2. use tokio::prelude::*;
  3. use tokio::net::TcpListener;
  4. # fn main() {
  5. // Set up a listening socket, just like in std::net
  6. let addr = "127.0.0.1:12345".parse().unwrap();
  7. let listener = TcpListener::bind(&addr)
  8. .expect("unable to bind TCP listener");
  9. // Listen for incoming connections.
  10. // This is similar to the iterator of incoming connections that
  11. // .incoming() from std::net::TcpListener, produces, except that
  12. // it is an asynchronous Stream of tokio::net::TcpStream instead
  13. // of an Iterator of std::net::TcpStream.
  14. let incoming = listener.incoming();
  15. // Since this is a Stream, not an Iterator, we use the for_each
  16. // combinator to specify what should happen each time a new
  17. // connection becomes available.
  18. let server = incoming
  19. .map_err(|e| eprintln!("accept failed = {:?}", e))
  20. .for_each(|socket| {
  21. // Each time we get a connection, this closure gets called.
  22. // We want to construct a Future that will read all the bytes
  23. // from the socket, and write them back on that same socket.
  24. //
  25. // If this were a TcpStream from the standard library, a read or
  26. // write here would block the current thread, and prevent new
  27. // connections from being accepted or handled. However, this
  28. // socket is a Tokio TcpStream, which implements non-blocking
  29. // I/O! So, if we read or write from this socket, and the
  30. // operation would block, the Future will just return NotReady
  31. // and then be polled again in the future.
  32. //
  33. // While we *could* write our own Future combinator that does an
  34. // (async) read followed by an (async) write, we'll instead use
  35. // tokio::io::copy, which already implements that. We split the
  36. // TcpStream into a read "half" and a write "half", and use the
  37. // copy combinator to produce a Future that asynchronously
  38. // copies all the data from the read half to the write half.
  39. let (reader, writer) = socket.split();
  40. let bytes_copied = tokio::io::copy(reader, writer);
  41. let handle_conn = bytes_copied.map(|amt| {
  42. println!("wrote {:?} bytes", amt)
  43. }).map_err(|err| {
  44. eprintln!("I/O error {:?}", err)
  45. });
  46. // handle_conn here is still a Future, so it hasn't actually
  47. // done any work yet. We *could* return it here; then for_each
  48. // would wait for it to complete before it accepts the next
  49. // connection. However, we want to be able to handle multiple
  50. // connections in parallel, so we instead spawn the future and
  51. // return an "empty" future that immediately resolves so that
  52. // Tokio will _simultaneously_ accept new connections and
  53. // service this one.
  54. tokio::spawn(handle_conn)
  55. });
  56. // The `server` variable above is itself a Future, and hasn't actually
  57. // done any work yet to set up the server. We need to run it on a Tokio
  58. // runtime for the server to really get up and running:
  59. tokio::run(server);
  60. # }

More examples can be found here.

Next up: Reading and Writing Data