> Do you need to keep custom structures for each network socket that maintains a snapshot of the state of how things are progressing?
That's one way of doing it.
Ideally, you track state for every connection (you need to do this even with async/await, it's just you need to be explicit) and you dispatch even handling to a thread pool using a command queue. One thread is your demultiplexor calling epoll/kqueue/MsgReceive/waitflor and all your other threads are workers in a pool, and communication is through a synchronized queue.
It's not fancy. It's low overhead and fairly easy to reason about.