Python Network Programming: A Complete Practical Guide
Python Programming
Python Network Programming: Build Real Clients & Servers
Go from zero to running your own TCP echo server, HTTP client, and async chat backend — with hands-on code you can run in two terminals today.
6
Learning phases
15+
Runnable examples
4
Practice projects
0
Paid libs required
Quick wins — What you will learn
IP, ports & sockets (TCP vs UDP)
TCP echo server + client
Multi-client with threads & asyncio
HTTP, TLS & secure connections
Your learning path
Pro tip: For production web apps, use FastAPI or Django. Learn raw sockets first — you will understand timeouts, connection pools, and WebSockets much faster.
Before you start
You will need
- • Python 3.10 or newer installed
- • Basic Python (functions, classes, exceptions)
- • Terminal access to run two processes (server + client)
- • Optional:
pip install requestsfor HTTP examples
Helpful tools
- •
netstat/ss— see open ports - •
telnet host portornc— quick connectivity tests - • Wireshark — inspect packets (advanced)
Network basics every Python developer should know
When your Python program talks to another machine, it uses a stack of protocols. You rarely touch layers 1–3 directly; most app code lives at the transport and application layers.
┌─────────────────────────────────────────┐
│ Application HTTP · DNS · your app │ ← you write here
├─────────────────────────────────────────┤
│ Transport TCP │ UDP │ ← socket module
├─────────────────────────────────────────┤
│ Network IP (192.168.x.x) │
├─────────────────────────────────────────┤
│ Link Wi‑Fi · Ethernet │ ← OS / hardware
└─────────────────────────────────────────┘
IP address + port = endpoint
A socket is identified by (IP, port, protocol). Example: your browser connects to 142.250.80.46:443 (Google, HTTPS).
- Connection handshake first
- Guaranteed order & delivery
- Retransmits lost packets
HTTP · SSH · MongoDB · most APIs
- No connection setup
- Lower latency
- May lose or reorder packets
DNS · gaming · video · broadcasts
How TCP connects (3-way handshake)
1. SYN
Client → Server
2. SYN-ACK
Server → Client
3. ACK
Connection ready
Ports you will see every day
| Term | Meaning |
|---|---|
127.0.0.1 |
Loopback — same machine only (localhost) |
0.0.0.0 |
Bind — listen on all network interfaces |
| Port | 0–65535; ports < 1024 often need admin on Linux |
| Hostname | Resolved to IP via DNS (socket.gethostbyname) |
The Python socket module
The standard library socket module wraps Berkeley sockets — the same API used in C, Go, and Rust.
Server flow
Client flow
import socket
# IPv4 + TCP (most common)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Phase 1 — TCP client & server
~10 min · Your first working network app
Step 1: Minimal TCP echo server
This server listens on port 9999, accepts one connection at a time, and echoes bytes back.
# server.py
import socket
HOST = "127.0.0.1" # localhost only — safe for learning
PORT = 9999
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((HOST, PORT))
server.listen(5)
print(f"Listening on {HOST}:{PORT}...")
conn, addr = server.accept()
with conn:
print(f"Connected by {addr}")
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data) # echo back
SO_REUSEADDR: Allows rebinding immediately after restart instead of waiting for TIME_WAIT.
Step 2: TCP client
Run the server in one terminal, then run the client in another:
# client.py
import socket
HOST = "127.0.0.1"
PORT = 9999
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((HOST, PORT))
sock.sendall(b"Hello from Python client!")
response = sock.recv(1024)
print("Server replied:", response.decode())
Try it yourself — 60 seconds
- Save
server.pyandclient.py - Terminal 1:
python server.py - Terminal 2:
python client.py
Expected output
# server.py
Listening on 127.0.0.1:9999...
Connected by ('127.0.0.1', 54321)
# client.py
Server replied: Hello from Python client!
Step 3: Send and receive strings safely
Sockets work with bytes, not str. Always encode before send and decode after recv. For structured messages, prefix length or use a delimiter/newline protocol.
def send_msg(sock: socket.socket, text: str) -> None:
sock.sendall(text.encode("utf-8"))
def recv_line(sock: socket.socket, bufsize: int = 4096) -> str:
data = sock.recv(bufsize)
if not data:
raise ConnectionError("Peer closed connection")
return data.decode("utf-8")
Important: recv(1024) may return fewer than 1024 bytes. For large payloads, loop until you have the full message or use a framing scheme.
Phase 2 — UDP (connectionless)
~5 min · Fire-and-forget messaging
UDP has no connect() handshake on the server side — you recvfrom() and reply with sendto().
UDP server
# udp_server.py
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 9998))
print("UDP server on 9998...")
while True:
data, addr = sock.recvfrom(1024)
print(f"From {addr}: {data.decode()}")
sock.sendto(b"ACK: " + data, addr)
UDP client
# udp_client.py
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(b"Ping", ("127.0.0.1", 9998))
reply, _ = sock.recvfrom(1024)
print(reply.decode())
Phase 3 — Handling multiple clients
~8 min · Scale beyond one connection
A single-threaded server blocks on accept() — only one client at a time. Pick your strategy:
Thread per client
Simple for moderate load.
Best for: learning, <100 clientsselect / poll
One thread, many sockets.
Best for: legacy serversasyncio
Thousands of idle connections.
Best for: chat, WebSocketsThreaded echo server
# threaded_server.py
import socket
import threading
HOST, PORT = "127.0.0.1", 9999
def handle_client(conn: socket.socket, addr):
with conn:
print(f"Thread {threading.current_thread().name} serving {addr}")
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
def main():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((HOST, PORT))
server.listen()
print(f"Threaded server on {HOST}:{PORT}")
while True:
conn, addr = server.accept()
t = threading.Thread(target=handle_client, args=(conn, addr), daemon=True)
t.start()
if __name__ == "__main__":
main()
Phase 4 — HTTP networking
~8 min · How the web actually works
HTTP runs on top of TCP (port 80 or 443). You can craft raw HTTP by hand to learn the protocol, but production code uses higher-level libraries.
Raw HTTP over a socket (educational)
import socket
host = "example.com"
port = 80
request = (
f"GET / HTTP/1.1\r\n"
f"Host: {host}\r\n"
f"Connection: close\r\n"
f"\r\n"
).encode()
with socket.create_connection((host, port)) as sock:
sock.sendall(request)
chunks = []
while True:
part = sock.recv(4096)
if not part:
break
chunks.append(part)
response = b"".join(chunks).decode("utf-8", errors="replace")
print(response[:500]) # status line + headers + start of body
urllib (standard library)
from urllib.request import urlopen
from urllib.error import URLError, HTTPError
url = "https://httpbin.org/get"
try:
with urlopen(url, timeout=10) as resp:
print("Status:", resp.status)
print("Headers:", dict(resp.headers))
body = resp.read().decode()
print(body[:300])
except HTTPError as e:
print("HTTP error:", e.code, e.reason)
except URLError as e:
print("Network error:", e.reason)
requests library (recommended for apps)
pip install requests
import requests
resp = requests.get(
"https://api.github.com/users/python",
timeout=10,
headers={"Accept": "application/json"},
)
resp.raise_for_status()
data = resp.json()
print(data["login"], data.get("public_repos"))
Phase 5 — TLS / SSL encryption
~7 min · Encrypt traffic in transit
Wrap a TCP socket with the ssl module to get HTTPS-style encryption. Never invent your own crypto — use TLS.
TLS client (connect to HTTPS server)
import socket
import ssl
hostname = "www.python.org"
port = 443
context = ssl.create_default_context() # verifies server certificate
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print("TLS version:", ssock.version())
ssock.sendall(b"GET / HTTP/1.1\r\nHost: www.python.org\r\n\r\n")
print(ssock.recv(2048).decode()[:400])
Production tip: Use requests or httpx for HTTPS — they handle certificates, redirects, and timeouts correctly.
TLS server (self-signed for local testing)
Generate a cert with OpenSSL, then wrap the server socket:
OpenSSL: create self-signed cert (click to expand)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=localhost"
import socket
import ssl
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain("cert.pem", "key.pem")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(("127.0.0.1", 10023))
sock.listen(5)
with context.wrap_socket(sock, server_side=True) as ssock:
conn, addr = ssock.accept()
with conn:
data = conn.recv(1024)
conn.sendall(b"Secure echo: " + data)
Phase 6 — asyncio streams (modern Python)
~8 min · High-concurrency servers
asyncio uses a single thread and an event loop. asyncio.start_server is the high-level API for TCP servers.
Async echo server
# async_server.py
import asyncio
async def handle(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
addr = writer.get_extra_info("peername")
print(f"Client connected: {addr}")
while True:
data = await reader.read(1024)
if not data:
break
writer.write(data)
await writer.drain()
writer.close()
await writer.wait_closed()
async def main():
server = await asyncio.start_server(handle, "127.0.0.1", 9997)
addrs = ", ".join(str(s.getsockname()) for s in server.sockets)
print(f"Serving on {addrs}")
async with server:
await server.serve_forever()
asyncio.run(main())
Async client
# async_client.py
import asyncio
async def main():
reader, writer = await asyncio.open_connection("127.0.0.1", 9997)
writer.write(b"Hello asyncio!")
await writer.drain()
reply = await reader.read(1024)
print("Reply:", reply.decode())
writer.close()
await writer.wait_closed()
asyncio.run(main())
Which Python module should I use?
socket
Raw TCP/UDP
asyncio
Async TCP servers
urllib
Simple HTTP GET
requests
Production HTTP client
ssl
TLS on sockets
httpx / aiohttp
Async HTTP
Choosing the right approach
| Use case | Best tool |
|---|---|
| REST API consumer | requests or httpx |
| Custom TCP protocol | socket or asyncio streams |
| Web app / JSON API server | FastAPI, Flask, Django |
| DNS / discovery / gaming | UDP sockets |
| Encrypted custom protocol | ssl wrapping socket + asyncio |
Self-check quiz
Click each question to reveal the answer. Be honest — if you miss one, re-read that section.
What is the difference between TCP and UDP? ▼
Why must you use bytes with sockets, not str?
▼
.encode("utf-8") before send; decode after recv.
When should you use asyncio instead of threads? ▼
What does 127.0.0.1 mean vs 0.0.0.0 when binding?
▼
127.0.0.1 = localhost only. 0.0.0.0 = listen on all network interfaces (use with firewall care).
Common mistakes to avoid
Assuming one recv() returns a full message
Forgetting to set socket timeouts (hangs forever)
Binding to 0.0.0.0 on untrusted networks without a firewall
Mixing threads and asyncio on the same socket
Sending str instead of bytes over sockets
Disabling TLS certificate verification in production
# Always set a timeout on blocking sockets
sock.settimeout(30.0)
# Prefer context managers
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
...
Practice projects
Level up by building these — each maps to a phase in this guide.
Line-based chat server
Broadcast each line to all clients. Use \n as delimiter. → Phase 3
Port scanner
connect_ex() on ports 1–1024 with a thread pool. → Phase 1 & 3
File transfer over TCP
Send 8-byte size header, then chunks + SHA-256 verify. → Phase 1
Async weather fetcher
Fetch 5 cities concurrently with asyncio + aiohttp. → Phase 4 & 6
Interactive learning checklist
Check off skills as you master them — your browser will remember (local only).
You're ready to go deeper
Python network programming starts with sockets: bind, listen, connect, send, and receive bytes. Layer on HTTP, TLS, and asyncio as you grow.
Next steps: build the chat server → try FastAPI → explore WebSockets. The socket mental model stays with you.