COMPUTER NETWORKS / 6. SOCKETS & WEBSOCKETS

Sockets & WebSockets

Raw network programming and real-time bidirectional communication


EXPLANATION

A socket is the OS abstraction for a network endpoint. It's a file descriptor connected to a network connection. All network communication — HTTP, SSH, DNS, everything — is built on sockets.

The socket API (Berkeley sockets, invented in 1983, still unchanged):
• socket() → create a new socket (specify AF_INET for IPv4, SOCK_STREAM for TCP)
• bind() → attach socket to an IP:port (server side)
• listen() → start accepting connections (server side, sets backlog queue size)
• accept() → block until a client connects, return a new socket for that connection
• connect() → initiate connection to server (client side, triggers TCP handshake)
• send()/recv() → transmit/receive data
• close() → close the connection (triggers TCP FIN)

Blocking vs Non-blocking sockets:
• Blocking (default): recv() blocks until data arrives. accept() blocks until a client connects
• Non-blocking: operations return immediately with an error if they'd block
• Async I/O (asyncio, select, epoll): efficient non-blocking I/O — one thread handles thousands of connections

WebSockets — HTTP's upgrade to full-duplex communication:
• HTTP is half-duplex: client requests, server responds. Client must request again for new data
• WebSocket is full-duplex: once connected, either side can send at any time without the other requesting
• Connection starts as HTTP, then upgrades:
  Client sends: "Upgrade: websocket"
  Server responds: "101 Switching Protocols"
  Now it's a persistent TCP connection with WebSocket framing
• Used for: chat apps, live dashboards, collaborative editing, real-time games, live notifications

FastAPI supports WebSockets natively with @app.websocket(). The client connects once and the server can push updates as events happen — no polling needed.

Socket options:
• SO_REUSEADDR → allow binding to a port that's in TIME_WAIT (essential for dev)
• SO_KEEPALIVE → detect dead connections
• TCP_NODELAY (Nagle's algorithm off) → send packets immediately (important for latency-sensitive apps)

DIAGRAM

TCP SOCKET LIFECYCLE:
  Server                          Client
  socket()                        socket()
  bind(8000)                      connect(server:8000) ─→ SYN
  listen()              SYN-ACK ←──────────────────────────
  accept() ←─────────────────────────────── ACK
  [new socket]
  recv()  ←───────────── data ──────────── send()
  send()  ─────────────→ data ──────────── recv()
  close() ─────────────→ FIN  ──────────── close()

  HTTP vs WEBSOCKET:
  HTTP (request-response):
  Client →→→ Request → Server
  Client ←←← Response ← Server
  Client →→→ Request → Server   (must request again)
  Client ←←← Response ← Server

  WebSocket (full-duplex):
  Client ←──────────────────── Server push
  Client ───────────────────→  Client message
  Client ←──────────────────── Server push
  (both sides can send anytime, one persistent connection)

CODE

PYTHON
1# ── WebSocket server with FastAPI ─────────────────────
2from fastapi import FastAPI, WebSocket, WebSocketDisconnect
3from typing import List
4
5app = FastAPI()
6
7# Connection manager for broadcasting
8class ConnectionManager:
9 def __init__(self):
10 self.active: List[WebSocket] = []
11
12 async def connect(self, ws: WebSocket):
13 await ws.accept()
14 self.active.append(ws)
15 print(f"Client connected. Total: {len(self.active)}")
16
17 def disconnect(self, ws: WebSocket):
18 self.active.remove(ws)
19 print(f"Client disconnected. Total: {len(self.active)}")
20
21 async def send_personal(self, message: str, ws: WebSocket):
22 await ws.send_text(message)
23
24 async def broadcast(self, message: str):
25 for connection in self.active:
26 await connection.send_text(message)
27
28manager = ConnectionManager()
29
30@app.websocket("/ws/{client_id}")
31async def websocket_endpoint(ws: WebSocket, client_id: str):
32 await manager.connect(ws)
33 try:
34 while True:
35 data = await ws.receive_text()
36 await manager.broadcast(f"[{client_id}]: {data}")
37 except WebSocketDisconnect:
38 manager.disconnect(ws)
39 await manager.broadcast(f"[{client_id}] left the chat")
40
41# ── WebSocket client (Python) ──────────────────────────
42import asyncio
43import websockets
44
45async def ws_client():
46 uri = "ws://localhost:8000/ws/kamran"
47 async with websockets.connect(uri) as ws:
48 await ws.send("Hello everyone!")
49 response = await ws.recv()
50 print(f"Got: {response}")
51
52# asyncio.run(ws_client())
53
54# ── Raw TCP echo server ────────────────────────────────
55import socket, threading
56
57def handle_client(conn, addr):
58 print(f"Connected: {addr}")
59 with conn:
60 while True:
61 data = conn.recv(1024)
62 if not data:
63 break
64 conn.send(data) # echo back
65 print(f"Disconnected: {addr}")
66
67def run_echo_server(port=9999):
68 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
69 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
70 s.bind(("", port))
71 s.listen()
72 print(f"Echo server on :{port}")
73 while True:
74 conn, addr = s.accept()
75 threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()
← PREV5. HTTP & HTTPSNEXT →7. Network Security