If all data is sent through the coordinator process, then that coordinator process becomes a bottleneck and a completely unnecessary level of buffering. So your first solution should be avoided.
There are a couple of alternatives to distribute the load over multiple worker processes:
Have one accept()
loop in the coordinator process, then fork()
off a worker process within that loop to handle the accepted socket. This is the classic daemon design and is easy to implement in C or Perl. But it can be difficult to implement correctly with Python because forking has surprising interactions with multithreading – and your Python program might be running more threads than you would expect.
Have one accept()
loop in the coordinator process, then send the accepted socket's file descriptor to the worker process over an AF_UNIX socket with an SCM_RIGHTS message. This is similar to dup()
ing the fd across a process boundary. This can be an elegant solution that allows the coordinator process to decide which worker process will handle the connection.
Have all worker processes run their own accept()
loop, but let them listen on a shared port by using the SO_REUSEPORT flag. The kernel then decides which worker process receives an incoming connection. This is most appropriate for a load balancing scenario because the coordinator process does not interfere with the connection, but this does not work with all kinds of sockets.
This is similar to your second solution where you start to listen on a port and then fork the worker processes, except that it is not necessary to start listening before forking and that either solution will only work with SO_REUSEPORT.