Synchronous and asynchronous networking in rust
When we’re building stuff for networking in rust we are usually always going to be dealing with the transport or application layers. So when we’re trying to build some protocol (QUIC, TCP) we’re building on top of the network layer. In rust you’re given access to API’s by the operating system that you’re using. The network layer is exposing this thing called a socket to you as the way that you can communicate down through the lower level networking stacks. Sockets allow you to send data back and forth between processes on some IP address and port. These sockets are file descriptors. In Unix systems, the sockets expose a file descriptor API. The operating system Kernel manages a table of running processes. What the socket is pointing to, is a descriptor table in the kernel.
Brief note on the network layer and the kernel
I’m only going to go in to a little bit of detail about the kernel here. The kernel is part of your operating system and has full controll over the hardware of the computer. The kernel will manage things like memory, CPU time etc. The kernel has a subsystem that manages processes. These processes are just tasks that some piece of software has asked your operating system to perform. The subsystem that manages the process, keeps track of each process using a process descriptor data structure. If you want to get information about what a process is doing, or what data it holds, then you can get that information through this process descriptor data structure. Deeper in to this process descriptor you have a file descriptor table. This maps the the file descriptors to the enteres structure in memory.
So when you are getting some file from a socket on some IP address. You are communicating with the kernel in this way. By accessing the exposed file descriptors in the operating system. These are managed by the kernel, so instead of dealing with the specifics of each kernel, you deal with a standard API that they expose. In this case it is an integer file descriptor.
Asynchronous sockets
If you’re using synchronous sockets, it means that you need to wait for each process to complete before you can continue. You have to continue to spin up more and more threads in order to be able to handle the incoming client requests(in the case of a server). The sockets in this case are said to be blocking. If we want to get around this, then we want to switch to some asynchronous system that allows all connection requests to be returned straight away. When the request(say for a file) is completed, then the action is completed later. This frees up other tasks that might be shorter or less expensive to run.
But what happens in the process of trying to complete some action and having it fail because it takes some time? If you make some request, you will get back som guarentee that you’re request is being processed, but how do we know when to check back for when it’s done. Polling is the system that helps us manage this. Polling is the process of checking if a request made to some socket can be completed. But polling also needs to keep track of what we have read from a socket operation so far. So if you have lots of client connections and lots of state to track in terms of what you have or havent already read, you get a complicated order or processes to make it all work.
Unix based operating systems can help us here by offering a polling mechanism. They tell us whether we can read or write to the socket. These are things such as epoll in linux and poll and select on all other Unix. epoll is more efificient because it tracks interested parties in O(1) time instead of a more constly action like poll or select.
Mio
Because we don’t want to have to deal with all of the complex actions that are going on in polling many client request to sockets, we can use crates that are provided to us in rust. Mio is a low level crate for such things. You can run a loop on the socket, and keep checking if it’s complete, but this is inefficient. epoll is a multiplexing file descriptor API that is available on Linux. If you’re using MacOS its kqueue, and on windows you have IO completion ports. Mio provides you with a non blocking primitives library for networking such as TCP or UDP.