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.

ITPUB
ITPUB
ITPUB
How to Build a Concurrent Python Web Server with fork() and Sockets

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Web serverUnixforkSocketsfile-descriptors
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.