Sockets are the foundation of all network communication. HTTP, SSH, DNS, and every other network protocol uses sockets under the hood. In this lab, you'll learn how sockets work by building simple TCP and UDP servers and clients.
What you'll learn:
- What sockets are and how they work
- TCP vs UDP differences
- Building basic TCP server/client
- Building basic UDP server/client
- How HTTP actually works (spoiler: it's just TCP!)
- Foundation for building REST APIs
A socket is an endpoint for network communication. Think of it as a phone connection:
IP Address = Phone Number
Port = Extension Number
Socket = The actual phone call
Every network connection has:
- Source IP + Source Port
- Destination IP + Destination Port
Example:
Your browser: 192.168.1.100:54321 → Google: 142.250.80.46:443
| Port | Protocol | Description |
|---|---|---|
| 22 | SSH | Secure shell |
| 53 | DNS | Domain name lookup |
| 80 | HTTP | Web traffic |
| 443 | HTTPS | Encrypted web |
| 514 | Syslog | Logging |
| Feature | TCP | UDP |
|---|---|---|
| Connection | Required (handshake) | Connectionless |
| Reliability | Guaranteed delivery | No guarantee |
| Order | Packets in order | May arrive out of order |
| Speed | Slower (overhead) | Faster (no overhead) |
| Use Cases | HTTP, SSH, FTP | DNS, Streaming, Syslog |
Analogy:
- TCP = Phone call (connection required, reliable)
- UDP = Sending a postcard (no connection, might get lost)
TCP requires a connection before data transfer. Server listens, client connects.
Create tcp_server.py:
import socket
# Create a TCP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind to address and port
HOST = '127.0.0.1' # localhost
PORT = 8080
server_socket.bind((HOST, PORT))
# Listen for incoming connections (max 5 in queue)
server_socket.listen(5)
print(f"TCP Server listening on {HOST}:{PORT}")
# Connection counter
connection_count = 0
# Keep server running
while True:
# Accept a connection (blocks until client connects)
client_socket, client_address = server_socket.accept()
connection_count += 1
print(f"\nConnection #{connection_count} from {client_address}")
# Receive data from client
data = client_socket.recv(1024) # Buffer size 1024 bytes
print(f"Received: {data.decode()}")
# Send response with counter to client
response = f"Hello! You are connection #{connection_count}"
client_socket.send(response.encode())
# Close this client connection (but keep server running)
client_socket.close()Run it in a screen session:
# Start a screen session
screen -S tcp_server
# Run the server
python tcp_server.py
# Detach from screen: Press Ctrl+A, then D
# Server keeps running in background
# screen -list # shows all screens running To reattach later:
screen -r tcp_serverCreate tcp_client.py:
import socket
# Create a TCP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to server
HOST = '127.0.0.1'
PORT = 8080
client_socket.connect((HOST, PORT))
print(f"Connected to {HOST}:{PORT}")
# Send message to server
message = "Hello from client!"
client_socket.send(message.encode())
# Receive response from server
data = client_socket.recv(1024)
print(f"Received: {data.decode()}")
# Close connection
client_socket.close()Run it multiple times:
python tcp_client.py
# Output: Received: Hello! You are connection #1
python tcp_client.py
# Output: Received: Hello! You are connection #2
python tcp_client.py
# Output: Received: Hello! You are connection #3Watch the server output:
# Reattach to see the counter incrementing
screen -r tcp_serverCleanup when done:
# Reattach to the screen session
screen -r tcp_server
# Stop the server: Press Ctrl+C
# Exit the screen session: type 'exit' or press Ctrl+D
# Or kill the screen session from outside:
screen -X -S tcp_server quitTCP SERVER TCP CLIENT
----------- -----------
1. socket()
2. bind(HOST, PORT)
3. listen()
4. accept() [WAITING...] 1. socket()
2. connect(HOST, PORT)
5. [CONNECTION ESTABLISHED] 3. [CONNECTION ESTABLISHED]
6. recv() ← "Hello from client!" 4. send("Hello from client!")
7. send("Hello from server!") → 5. recv() ← "Hello from server!"
8. close() 6. close()
Key points:
socket.AF_INET= IPv4socket.SOCK_STREAM= TCP- Server uses
bind(),listen(),accept() - Client uses
connect() - Both use
send()andrecv() - Always close sockets!
UDP has no connection. Just send and receive messages.
Create udp_server.py:
import socket
# Create a UDP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Bind to address and port
HOST = '127.0.0.1'
PORT = 9090
server_socket.bind((HOST, PORT))
print(f"UDP Server listening on {HOST}:{PORT}")
# Message counter
message_count = 0
# Keep server running
while True:
# Receive data (no accept() needed - UDP is connectionless!)
data, client_address = server_socket.recvfrom(1024)
message_count += 1
print(f"\nMessage #{message_count} from {client_address}: {data.decode()}")
# Send response back to client
response = f"Received! Message #{message_count}"
server_socket.sendto(response.encode(), client_address)Run it in a screen session:
# Start a screen session
screen -S udp_server
# Run the server
python udp_server.py
# Detach from screen: Press Ctrl+A, then D
# Server keeps running in backgroundTo reattach later:
screen -r udp_serverCreate udp_client.py:
import socket
# Create a UDP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Server address
HOST = '127.0.0.1'
PORT = 9090
# Send message (no connect() needed!)
message = "Hello from UDP client!"
client_socket.sendto(message.encode(), (HOST, PORT))
print(f"Sent to {HOST}:{PORT}")
# Receive response
data, server_address = client_socket.recvfrom(1024)
print(f"Received from {server_address}: {data.decode()}")
# Close socket
client_socket.close()Run it multiple times:
python udp_client.py
# Output: Received from ...: Received! Message #1
python udp_client.py
# Output: Received from ...: Received! Message #2
python udp_client.py
# Output: Received from ...: Received! Message #3Watch the server output:
# Reattach to see the counter incrementing
screen -r udp_serverCleanup when done:
# Reattach to the screen session
screen -r udp_server
# Stop the server: Press Ctrl+C
# Exit the screen session: type 'exit' or press Ctrl+D
# Or kill the screen session from outside:
screen -X -S udp_server quitTCP:
# Server
server.bind((HOST, PORT))
server.listen()
client, addr = server.accept() # Wait for connection
data = client.recv(1024)
client.send(response)
# Client
client.connect((HOST, PORT)) # Establish connection
client.send(message)
data = client.recv(1024)UDP:
# Server
server.bind((HOST, PORT))
data, addr = server.recvfrom(1024) # No connection
server.sendto(response, addr)
# Client
client.sendto(message, (HOST, PORT)) # No connection
data, addr = client.recvfrom(1024)Key differences:
- UDP uses
sendto()/recvfrom()(includes address) - TCP uses
send()/recv()(connection already established) - UDP has no
connect(),accept(), orlisten()
HTTP is just text over TCP on port 80 (or 443 for HTTPS).
First, install and start Apache (httpd):
# Install Apache
sudo dnf install httpd -y
# Start Apache service
sudo systemctl start httpd
# Verify it's running
sudo systemctl status httpd
# Apache is now serving content on port 80Create http_manual_client.py:
import socket
# Create TCP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to local Apache server
HOST = 'localhost'
PORT = 80
client_socket.connect((HOST, PORT))
print(f"Connected to {HOST}:{PORT}")
# Send raw HTTP request
http_request = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
client_socket.send(http_request.encode())
print("Sent HTTP request:")
print(http_request)
# Receive HTTP response
response = b""
while True:
chunk = client_socket.recv(4096)
if not chunk:
break
response += chunk
client_socket.close()
# Print response
print("\nReceived HTTP response:")
print(response.decode())Run it:
python http_manual_client.pyYou'll see:
HTTP/1.1 200 OK
Date: Mon, 16 Dec 2025 10:30:00 GMT
Server: Apache/2.4.x
Content-Type: text/html
...
<!doctype html>
<html>
...
Note: You might see "Apache Test Page", "It works!", or even a 403 Forbidden response. All are normal - it shows Apache is running and responding with real HTTP. A 403 just means directory listing is disabled (security feature).
This is exactly what curl does!
# Run curl to see the same thing
curl -v http://localhost/
# curl is just a fancy HTTP client built on sockets!What you just built:
- ✅ You created your own
curl - ✅ Raw TCP socket connection
- ✅ Manual HTTP request formatting
- ✅ Response parsing
curl does the same thing - it's just HTTP over TCP sockets with better formatting and features!
Before building our own HTTP server, stop Apache to free up port 80:
# Stop Apache service
sudo systemctl stop httpd
# Verify it's stopped
sudo systemctl status httpdNote: Make sure your security group has port 8000 open if testing from outside your VM!
Create http_simple_server.py:
import socket
# Create TCP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
HOST = '0.0.0.0' # Listen on all interfaces
PORT = 8000
server_socket.bind((HOST, PORT))
server_socket.listen(5)
print(f"HTTP Server running on http://{HOST}:{PORT}")
print("Open your browser and visit the URL above")
print("Press Ctrl+C to stop")
# Request counter
request_count = 0
# Keep server running
while True:
# Accept connection
client_socket, client_address = server_socket.accept()
request_count += 1
print(f"\nRequest #{request_count} from {client_address}")
# Receive HTTP request
request = client_socket.recv(1024).decode()
print("Request received:")
print(request.split('\r\n')[0]) # Print just the request line
# Send HTTP response
http_response = f"""HTTP/1.1 200 OK
Content-Type: text/html
Connection: close
<!DOCTYPE html>
<html>
<head><title>Socket Server</title></head>
<body>
<h1>Hello from a Socket!</h1>
<p>This is raw HTTP over TCP.</p>
<p>No Flask, no http.server - just sockets!</p>
<p><strong>You are visitor #{request_count}</strong></p>
</body>
</html>
"""
client_socket.send(http_response.encode())
client_socket.close()Run it in a screen session:
# Start a screen session
screen -S http_server
# Run the server
python http_simple_server.py
# Detach from screen: Press Ctrl+A, then DTest it:
# From command line
curl http://localhost:8000
# Or open in browser (multiple times to see counter increment)
# http://<your-vm-ip>:8000Watch the server count requests:
screen -r http_serverCleanup when done:
# Reattach and stop
screen -r http_server
# Press Ctrl+C to stop server
# Type 'exit' to close screen
# Or kill from outside
screen -X -S http_server quitHTTP is a text protocol over TCP:
CLIENT SENDS:
-------------
GET /api/data HTTP/1.1
Host: example.com
User-Agent: Python
Accept: application/json
[blank line]
SERVER RESPONDS:
----------------
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 27
{"status": "success"}
Key parts:
- Request line:
GET /api/data HTTP/1.1 - Headers:
Host: example.com - Blank line:
\r\n\r\n(separates headers from body) - Body: (optional) JSON, HTML, etc.
This is what requests library does for you!
This is what Flask/http.server does for you!
Sockets can fail. Always handle errors.
import socket
def safe_tcp_client(host, port):
"""TCP client with error handling"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5) # 5 second timeout
sock.connect((host, port))
sock.send(b"Hello")
data = sock.recv(1024)
print(f"Received: {data.decode()}")
sock.close()
except socket.timeout:
print("Connection timed out")
except ConnectionRefusedError:
print("Connection refused - is server running?")
except socket.gaierror:
print("Invalid hostname")
except Exception as e:
print(f"Error: {e}")
# Test it
safe_tcp_client("127.0.0.1", 8080)Common errors:
ConnectionRefusedError- Server not runningsocket.timeout- Server not respondingsocket.gaierror- Bad hostname/IP
For this challenge, you will build a basic HTTP server from scratch using Python sockets. Your server must:
- Accept HTTP requests and parse the request line and headers.
- Respond with a simple HTML page that displays:
- The HTTP method (GET, POST, etc.)
- The path/page requested (e.g.,
/hello) - The browser/user-agent string
- Only respond with HTTP 200 OK for a few specific endpoints (e.g.,
/,/hello,/about). - For all other paths, return an HTTP 404 Not Found with a simple HTML error page.
GET /hello HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0
Accept: text/html
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<html>
<body>
<h1>Request Info</h1>
<p><strong>Method:</strong> GET</p>
<p><strong>Path:</strong> /hello</p>
<p><strong>Browser:</strong> Mozilla/5.0</p>
</body>
</html>
HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8
<html>
<body>
<h1>404 Not Found</h1>
<p>The page you requested does not exist.</p>
</body>
</html>
- Use only the Python standard library (
socket, etc.). - Parse the request line and headers manually.
- Extract the method, path, and User-Agent.
- Respond with the correct HTML and status code.
- Server should handle multiple requests (loop).
Tip: You can test your server with a browser or with curl.
You've completed this lab when you can:
- Explain what a socket is (IP + Port)
- Understand TCP vs UDP differences
- Create a TCP server that listens and accepts connections
- Create a TCP client that connects and sends data
- Create a UDP server and client
- Manually send raw HTTP requests over TCP
- Build a simple HTTP server returning HTML
- Build a port scanner
- Handle socket errors properly
- Complete the configuration server challenge
Sockets are the foundation:
- Every network protocol uses sockets
- HTTP, SSH, DNS - all built on sockets
TCP (SOCK_STREAM):
- Connection-oriented (handshake required)
- Reliable, ordered delivery
- Server:
bind()→listen()→accept()→recv()/send() - Client:
connect()→send()/recv() - Used for: HTTP, SSH, FTP, etc.
UDP (SOCK_DGRAM):
- Connectionless (no handshake)
- Unreliable, may lose packets
- Server:
bind()→recvfrom()/sendto() - Client:
sendto()/recvfrom() - Used for: DNS, Syslog, streaming
HTTP is just TCP:
- Text-based protocol over TCP port 80/443
- Request:
GET /path HTTP/1.1\r\nHeaders...\r\n\r\nBody - Response:
HTTP/1.1 200 OK\r\nHeaders...\r\n\r\nBody - Libraries like
requestsandFlaskhandle this for you
# TCP Server
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(1)
client, addr = server.accept()
data = client.recv(1024)
client.send(b"response")
client.close()
# TCP Client
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
client.send(b"message")
data = client.recv(1024)
client.close()
# UDP Server
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 9090))
data, addr = server.recvfrom(1024)
server.sendto(b"response", addr)
# UDP Client
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(b"message", ('127.0.0.1', 9090))
data, addr = client.recvfrom(1024)Congratulations! You now understand how network communication works at the socket level. This is the foundation for everything in network automation!