ch10s3_NetworkingWithSockets
Networking allows computers to exchange data. In Python, **sockets** provide a low-level interface for this communication, enabling you to build servers, clients, and protocols like HTTP, FTP, or custom systems.
Chapter 10: Advanced Topics — Networking with Sockets
🌐 Networking with Sockets — Communication Between Devices
Networking allows computers to exchange data. In Python, sockets provide a low-level interface for this communication, enabling you to build servers, clients, and protocols like HTTP, FTP, or custom systems.
🧩 1. What Are Sockets?
A socket is an endpoint of a two-way communication channel between programs running on a network.
Think of it as a phone line — both the caller (client) and receiver (server) need one to talk.
| Term | Description |
|---|---|
| IP Address | The unique address of a machine on a network (like a phone number). |
| Port | A number identifying a specific service or application (like an extension number). |
| Protocol | Defines how communication happens — usually TCP or UDP. |
🔗 2. TCP vs UDP
| Feature | TCP (SOCK_STREAM) | UDP (SOCK_DGRAM) |
|---|---|---|
| Type | Connection-oriented | Connectionless |
| Reliability | Guaranteed (retransmission) | Not guaranteed |
| Speed | Slower | Faster |
| Order | Preserved | Not preserved |
| Use Case | Web servers, file transfer | Streaming, games, IoT |
🖥️ 3. Building a TCP Server
A server listens for incoming connections, accepts them, and exchanges data.
import socket
HOST = 'localhost'
PORT = 8080
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.bind((HOST, PORT))
server_socket.listen(1)
print(f"Server listening on {HOST}:{PORT}...")
conn, addr = server_socket.accept()
with conn:
print(f"Connected by {addr}")
while True:
data = conn.recv(1024)
if not data:
break
print(f"Received: {data.decode()}")
conn.sendall(b"Hello from the server!")
💻 4. Building a TCP Client
A client connects to a server, sends data, and waits for a response.
import socket
HOST = 'localhost'
PORT = 8080
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
client_socket.connect((HOST, PORT))
message = "Hello from the client!"
client_socket.sendall(message.encode())
data = client_socket.recv(1024)
print(f"Server replied: {data.decode()}")
💡
recv(1024)means “receive up to 1024 bytes.” Always ensure your buffer is large enough.
🔁 5. Two-Way Example — Client and Server
import socket, threading
def server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('localhost', 8080))
s.listen()
print("Server waiting for connection...")
conn, addr = s.accept()
with conn:
print(f"Connected: {addr}")
while True:
data = conn.recv(1024)
if not data:
break
print("Received:", data.decode())
conn.sendall(b"Message received!")
def client():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as c:
c.connect(('localhost', 8080))
for msg in ["Hello", "How are you?", "Goodbye"]:
c.sendall(msg.encode())
print("Server:", c.recv(1024).decode())
# Run server and client concurrently
threading.Thread(target=server, daemon=True).start()
threading.Thread(target=client).start()
📡 6. UDP Communication — Faster but Unreliable
UDP is faster because it doesn’t establish a connection before sending data.
UDP Server
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(('localhost', 9090))
print("UDP Server listening...")
while True:
data, address = server_socket.recvfrom(1024)
print(f"Received {data.decode()} from {address}")
server_socket.sendto(b"Hello from UDP server!", address)
UDP Client
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('localhost', 9090)
client_socket.sendto(b"Hello UDP!", server_address)
data, _ = client_socket.recvfrom(1024)
print("Received:", data.decode())
client_socket.close()
⚡ UDP is ideal for real-time applications like online games or video streaming.
👥 7. Handling Multiple Clients — Threaded TCP Server
To handle several clients at once, use threading.
import socket, threading
def handle_client(conn, addr):
print(f"New connection: {addr}")
with conn:
while True:
data = conn.recv(1024)
if not data:
break
print(f"{addr}: {data.decode()}")
conn.sendall(b"Echo: " + data)
def start_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('localhost', 7070))
s.listen()
print("Server running on port 7070...")
while True:
conn, addr = s.accept()
thread = threading.Thread(target=handle_client, args=(conn, addr))
thread.start()
start_server()
🧮 8. Async Networking — Modern asyncio Example
For highly scalable applications, Python’s asyncio library allows non-blocking I/O without threads.
import asyncio
async def handle_client(reader, writer):
data = await reader.read(100)
message = data.decode()
writer.write(f"Echo: {message}".encode())
await writer.drain()
writer.close()
async def main():
server = await asyncio.start_server(handle_client, 'localhost', 8081)
async with server:
await server.serve_forever()
asyncio.run(main())
🧠 Async I/O handles thousands of connections efficiently (used by frameworks like FastAPI and aiohttp).
🔐 9. Security, Errors, and Cleanup
✅ Use with socket.socket(...) to ensure proper closure.
✅ Catch ConnectionResetError and handle gracefully.
✅ Avoid hardcoded ports (use configs or environment variables).
✅ Sanitize incoming data to avoid injection attacks.
⚖️ 10. Comparison Table
| Aspect | TCP | UDP | asyncio |
|---|---|---|---|
| Connection | Required (3-way handshake) | None | Non-blocking event loop |
| Reliability | Guaranteed | Not guaranteed | Managed by event loop |
| Speed | Moderate | Very fast | Scalable |
| Typical Use | Web apps, APIs | Games, sensors, logs | Servers, microservices |
💬 11. Real-World Example — Mini Chat Server
import socket, threading
clients = []
def handle_client(conn, addr):
print(f"Client connected: {addr}")
clients.append(conn)
while True:
data = conn.recv(1024)
if not data:
break
for client in clients:
if client != conn:
client.sendall(data)
clients.remove(conn)
conn.close()
def start_chat_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('localhost', 9000))
s.listen()
print("Chat server running on port 9000...")
while True:
conn, addr = s.accept()
threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()
start_chat_server()
Each connected client broadcasts messages to others — the foundation of real-time chat systems.
🧭 12. Best Practices
✅ Always close sockets (with statement).
✅ Use try/except for safe cleanup.
✅ Use threading or asyncio for multi-client apps.
✅ Choose TCP for reliability, UDP for speed.
✅ Never trust raw input — validate and sanitize.
✅ Prefer socketserver or frameworks like asyncio for production systems.
🧠 Summary
| Concept | Description | Example |
|---|---|---|
| Socket | Endpoint of communication between programs | socket.socket(AF_INET, SOCK_STREAM) |
| Server | Listens for incoming connections | bind(), listen(), accept() |
| Client | Connects to a server | connect(), send(), recv() |
| TCP | Reliable stream communication | Chat server |
| UDP | Fast, connectionless communication | IoT or games |
| asyncio | Asynchronous I/O for scalability | Modern servers |
Sockets are the foundation of all networked software — from web browsers to multiplayer games. Understanding them empowers you to build reliable, real-time, and scalable systems.