How to Build a Concurrent Python Web Server with fork() and Sockets
This tutorial walks you through creating a simple iterative Python web server, demonstrates its single‑request limitation, explains socket fundamentals, and then shows how to transform it into a concurrent server using the Unix fork() system call while handling backlog, duplicate descriptors, and zombie processes.
In the previous parts you built a minimal WSGI‑style server that handles a single HTTP GET request at a time. The server is iterative: it accepts a connection, processes the request, sleeps for 60 seconds, and only then returns to accept another client, causing other clients to wait.
To verify the single‑request behavior, the code was modified to add a time.sleep(60) after sending the response. Running two curl commands in separate terminals shows that the second request hangs until the first one finishes its sleep.
The tutorial then introduces the concept of sockets as communication endpoints. A TCP socket pair consists of a 4‑tuple {local_ip:local_port, remote_ip:remote_port}. The server creates a listening socket, binds it, sets SO_REUSEADDR, and calls listen(REQUEST_QUEUE_SIZE). The client creates a socket and calls connect with the server’s address.
Create a TCP/IP socket:
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)Optionally set socket options:
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)Bind the socket to an address: listen_socket.bind(SERVER_ADDRESS) Mark the socket as listening: listen_socket.listen(REQUEST_QUEUE_SIZE) After these steps the server enters a loop, calls accept() to obtain a client socket, reads the request, sends a response, and closes the client socket before returning to the top of the loop.
On the client side, after creating a socket, the program calls connect() with the server’s IP and port, sends the request, reads the response, and then closes the socket. The client does not need to call bind() or accept() because the kernel assigns an ephemeral port automatically.
The article also explains processes and file descriptors: a process is an executing program instance with a PID; the parent process (e.g., the Bash shell) has a PPID. File descriptors are integer handles returned by the kernel when opening files or sockets.
To achieve concurrency, the simplest Unix method is to call fork(). The tutorial provides webserver3c.py, a fork‑based concurrent server. When a client connects, the parent process forks a child; the child handles the request while the parent immediately returns to accept() to serve other clients. The fork() call returns twice: 0 in the child and the child’s PID in the parent.
Because the child inherits copies of the parent’s file descriptors, the kernel uses a reference count to decide when a descriptor can be closed. The parent closes its copy of the client socket, but the child’s copy keeps the count above zero, allowing the child to continue communication.
The BACKLOG parameter (passed to listen()) controls the size of the kernel’s pending‑connection queue. Increasing BACKLOG lets more clients wait for acceptance, but does not make the server truly concurrent.
If the server fails to close duplicate descriptors in both parent and child, the descriptor count never reaches zero, so the TCP FIN packet is never sent. Clients then remain in a hanging state, and the server eventually exhausts its available file descriptors (often 1024 by default). The tutorial demonstrates this by lowering the descriptor limit with ulimit -n 256 and spawning many parallel connections, causing the server to crash.
Another issue is zombie processes: after a child exits, the parent must reap it (e.g., with os.wait() or by handling SIGCHLD). Without reaping, the child remains in a defunct state, consuming a PID slot.
In summary, the article walks you from a single‑request iterative server to a fully concurrent forking server, covering socket creation, process forking, descriptor management, backlog tuning, and the importance of cleaning up both duplicate descriptors and zombie processes.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
