Quick start: Dựng kết nối hai chiều trong 5 phút
Cài đặt thư viện qua pip:
pip install websockets
Tạo file server.py để nhận và phản hồi tin nhắn tức thì từ client:
# server.py
import asyncio
import websockets
async def echo(websocket):
async for message in websocket:
print(f"[Server nhận]: {message}")
await websocket.send(f"Server phản hồi: {message}")
async def main():
async with websockets.serve(echo, "localhost", 8765):
print("Server đang lắng nghe tại ws://localhost:8765")
await asyncio.Future() # Giữ tiến trình chạy nền
if __name__ == "__main__":
asyncio.run(main())
Tiếp theo, tạo client.py để gửi dữ liệu thử nghiệm:
# client.py
import asyncio
import websockets
async def send_message():
uri = "ws://localhost:8765"
async with websockets.connect(uri) as websocket:
await websocket.send("Ping từ Client!")
response = await websocket.recv()
print(f"[Client nhận]: {response}")
if __name__ == "__main__":
asyncio.run(send_message())
Mở hai cửa sổ terminal. Chạy python server.py trước, sau đó chạy python client.py. Kết quả sẽ in ra màn hình gần như tức thì với độ trễ dưới 5ms.
HTTP Polling vs WebSocket: Khi nào nên đổi?
HTTP truyền thống hoạt động theo cơ chế Stateless Request-Response. Muốn cập nhật trạng thái liên tục, client phải dùng kỹ thuật Polling gửi request định kỳ 1–3 giây một lần.
Cách này bộc lộ điểm yếu lớn khi tải tăng cao:
- Overhead HTTP: Mỗi request mang theo 500–1000 bytes HTTP headers (Cookie, User-Agent, Auth). WebSocket chỉ tốn 2–6 bytes framing payload sau khi handshake xong.
- Tài nguyên máy chủ: 10.000 user polling mỗi giây sẽ tạo ra 10.000 request/giây lên server. Với WebSocket, bạn chỉ duy trì 10.000 TCP connection mở sẵn và truyền data khi có event phát sinh.
- Độ trễ (Latency): Polling luôn có độ trễ bằng chu kỳ polling. WebSocket đẩy dữ liệu xuống client ngay tại thời điểm sự kiện xảy ra.
Cơ chế Asyncio bên dưới thư viện websockets
Thư viện chạy hoàn toàn trên Event Loop của asyncio. Khi một connection chờ I/O mạng, CPU lập tức chuyển sang xử lý packet từ connection khác. Nhờ non-blocking I/O, một worker Python đơn luồng có thể duy trì hàng chục nghìn socket đồng thời mà tốn rất ít RAM.
Nâng cao: Xây dựng Broadcast Room cho nhiều user
Trường hợp phổ biến nhất là gửi data đồng thời tới nhiều subscriber, ví dụ bảng giá chứng khoán hoặc room chat.
Thư viện cung cấp sẵn hàm websockets.broadcast được tối ưu để đẩy message cho toàn bộ socket trong một set:
# chat_server.py
import asyncio
import websockets
CONNECTED_CLIENTS = set()
async def handler(websocket):
CONNECTED_CLIENTS.add(websocket)
client_ip = websocket.remote_address
print(f"+ Client kết nối: {client_ip} (Online: {len(CONNECTED_CLIENTS)})")
try:
async for message in websocket:
# Broadcast tới tất cả client đang active
websockets.broadcast(CONNECTED_CLIENTS, f"{client_ip[0]}: {message}")
except websockets.exceptions.ConnectionClosed:
pass
finally:
CONNECTED_CLIENTS.remove(websocket)
print(f"- Client ngắt kết nối: {client_ip} (Online: {len(CONNECTED_CLIENTS)})")
async def main():
async with websockets.serve(handler, "0.0.0.0", 8765):
print("Broadcast Server đang chạy tại cổng 8765...")
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())
3 lưu ý quan trọng khi deploy Production
1. Giữ kết nối qua Load Balancer (Heartbeat / Ping-Pong)
AWS ALB hay Cloudflare thường drop kết nối TCP nếu idle quá 60 giây. Hãy thiết lập ping định kỳ để giữ kết nối luôn sống:
# Gửi ping mỗi 20s, ngắt kết nối nếu 10s không có phản hồi pong
async with websockets.serve(
handler,
"0.0.0.0",
8765,
ping_interval=20,
ping_timeout=10
):
await asyncio.Future()
2. Tự động kết nối lại (Exponential Backoff) ở Client
Mạng 4G/Wi-Fi của người dùng thường xuyên chập chờn. Phía client cần bắt ngoại lệ và retry với khoảng giãn cách tăng dần (exponential backoff) để tránh làm nghẽn server:
# robust_client.py
import asyncio
import websockets
async def connect_with_retry(uri):
retry_delay = 1
max_delay = 30
while True:
try:
async with websockets.connect(uri) as websocket:
print("Kết nối WebSocket thành công!")
retry_delay = 1 # Reset delay sau khi kết nối ổn định
async for msg in websocket:
print(f"Data: {msg}")
except (websockets.exceptions.ConnectionClosed, OSError) as e:
print(f"Mất kết nối ({e}). Thử lại sau {retry_delay}s...")
await asyncio.sleep(retry_delay)
retry_delay = min(retry_delay * 2, max_delay)
3. Reverse Proxy qua Nginx và SSL (WSS)
Không nên public trực tiếp cổng Python ra Internet. Hãy đặt server sau Nginx để terminate SSL và chuyển tiếp request:
location /ws/ {
proxy_pass http://127.0.0.1:8765;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_read_timeout 300s;
}
Ngoài ra, hãy kiểm tra JWT Token ngay trong query param hoặc header ở bước handshake đầu tiên để chặn truy cập trái phép trước khi cấp phát tài nguyên socket.

