Building a Web Server with Sockets
A socket connection to port 80 establishes a reliable communication channel with a web server through a three-way handshake.
From Program to Web Server
A web browser can be understood as a program that opens a socket, connects to a web server, sends an HTTP request, receives the response in pieces, and displays the result. This lesson follows that process directly. The socket provides the communication channel, while HTTP defines the text-based request format sent through it.
Connection Before Communication
Creating a socket gives the program an object through which it can communicate. Calling connect() with a server and port begins the connection process. For a connection to port 80, the computer initiates a TCP three-way handshake: it sends a SYN packet, the server responds with SYN-ACK, and the computer sends ACK. Once this exchange completes, a reliable communication channel exists between the program's socket and the server's listening socket.
The code uses a placeholder server name so that the connection target is visible without assuming a particular real server. The important sequence is that the socket is created first and connect() is called with the remote server and port 80 second.
Anatomy of the GET Request
After the connection exists, the client sends an HTTP GET command. The request starts with GET, includes a resource path and an HTTP version, and ends with two consecutive line endings. In HTTP, the line-ending sequence is carriage return followed by line feed, written as \r\n. Therefore, the request terminator is \r\n\r\n.
request = "GET / HTTP/1.1\r\n\r\n" request_bytes = request.encode() sock.send(request_bytes)
Reading the Response Stream
The server response may arrive in multiple pieces rather than as one complete value. The client repeatedly calls recv(512), which retrieves up to 512 bytes at a time. Each returned chunk is decoded into a string before it is printed. The loop continues while data is returned. When the server has finished sending and closes its end of the connection, recv() produces an empty return, which tells the client to stop reading.
while True: chunk = sock.recv(512) if not chunk: break text = chunk.decode() print(text, end="")
The loop does not assume that one receive operation contains a complete HTTP response. It treats each receive as one chunk, processes that chunk, and then asks for another. The empty return is the stopping signal, so the client can finish even when the response requires many iterations.
A Complete Minimal Client
Trace a complete HTTP exchange
Construct a minimal Python client that connects to port 80, sends a GET request for the root resource, prints the response, and stops when the server closes its end of the connection.
Create and connect: Create a socket object and connect it to a remote server on port 80. The connection call initiates the three-way handshake.
Format the request: Build a request beginning with GET, include the root path and HTTP version, and finish with \r\n\r\n.
Convert and send: Encode the request string into bytes and send those bytes through the connected socket.
Receive repeatedly: Call recv(512) in a loop. Decode each non-empty chunk before printing it.
Stop and close: Break when recv() returns an empty value, then close the socket to finish its lifecycle.
The client follows the complete sequence: socket creation, connection, byte-encoded HTTP request, chunked response processing, end-of-response detection, and closure.
The program prints the response text continuously as chunks are decoded. The exact response content depends on the server contacted; the important behavior is that printing continues until recv() returns an empty value.Mistakes That Break the Exchange
Sending the request as an ordinary string
The socket request must be encoded as bytes before it is sent.
Fix:
Use sock.send(request.encode()).Ending the request with only one line ending
The HTTP request needs \r\n\r\n so the blank line signals the end of the headers.
Fix:
End the request with two consecutive \r\n sequences.Assuming one recv() call gets the complete response
The response arrives as a stream and recv(512) retrieves up to 512 bytes at a time.
Fix:
Use a loop and continue receiving until the return is empty.Printing received bytes without decoding
Received chunks are raw bytes and must be decoded into a string before printing.
Fix:
Decode each non-empty chunk before printing it.Adding a newline after every chunk
Each print call would add its own newline, potentially separating chunks that belong together.
Fix:
Use print(chunk.decode(), end="") to keep the chunks continuous.
Practice the Lifecycle
Write the missing lines for a client that has already created sock and connected it to a server on port 80. The client must send a GET request for the root resource, receive up to 512 bytes per iteration, decode each non-empty chunk, print continuously, and stop when the response ends.
Hints
- The request must end with \r\n\r\n before it is encoded.
- Use a loop because the response can arrive in multiple chunks.
- An empty return from recv() is the stopping condition.
What do you think happens?
What should happen when recv(512) returns an empty value?
Reveal answer
Answer: Break out of the receive loop
An empty return signals that the server has finished sending and closed its end of the connection, so there is no more response data to process.
Complete Exchange Checklist
- Create a socket object.
- Connect it to the remote server on port 80, allowing the three-way handshake to establish the channel.
- Build a GET request with a resource path and HTTP version.
- End the request with \r\n\r\n.
- Encode the request string into bytes and send it.
- Call recv(512) repeatedly because the response arrives in chunks.
- Stop when recv() returns an empty value.
- Decode each non-empty chunk before printing it.
- Close the socket after the exchange ends.
The central idea is to keep the socket lifecycle and HTTP message format distinct but connected. TCP provides the reliable channel after the handshake. HTTP supplies the GET request and its required line endings. The receive loop handles the response stream, while decoding turns each byte chunk into readable text.
Key Takeaways
- A connection to port 80 begins with a TCP three-way handshake involving SYN, SYN-ACK, and ACK.
- An HTTP GET request must be encoded as bytes and end with \r\n\r\n to mark the end of its headers.
- A response can arrive in many chunks, so the client repeatedly calls recv(512).
- Each non-empty byte chunk must be decoded before printing, and an empty return signals that reading should stop.
- Closing the socket completes the lifecycle after the request and response exchange.