WebSocket và Socket.io: Xây dựng realtime features cho chat, live dashboard và game web

3 tháng trước · 9 phút đọc
Tại sao polling HTTP không đủ cho realtime
HTTP hoạt động theo mô hình request-response: client hỏi, server trả lời, kết nối đóng. Muốn cập nhật dữ liệu mới? Client phải hỏi lại. Cứ vậy lặp - gọi là polling.
Với chat app 1000 người dùng đang chờ tin nhắn, polling mỗi 1 giây nghĩa là server xử lý 1000 request/giây - gần như toàn bộ là "không có gì mới". Lãng phí tài nguyên theo kiểu khó chấp nhận hơn là bạn nghĩ.
Polling vs WebSocket: polling tốn tài nguyên gấp nhiều lần dù dữ liệu không đổi
Long polling cải thiện một chút - server giữ kết nối mở đến khi có dữ liệu mới rồi mới trả về. Nhưng sau mỗi response, client phải tạo kết nối mới. Overhead vẫn cao, và xử lý timeout phức tạp.
WebSocket sinh ra để giải quyết đúng vấn đề này. Một kết nối TCP duy nhất, hai chiều, giữ mở suốt session. Server push data xuống bất cứ lúc nào mà không cần client hỏi trước. Đây là nền tảng của mọi ứng dụng realtime thực sự.
WebSocket thuần: hiểu từ nền tảng
Bắt đầu với WebSocket API của browser - không thư viện, không framework:
Phía server với Node.js dùng thư viện ws:
Một kết nối WebSocket duy nhất phục vụ cả gửi lẫn nhận
WebSocket thuần nhẹ và đơn giản, nhưng bạn phải tự xây mọi thứ: quản lý rooms, tái kết nối, heartbeat để phát hiện connection chết, xử lý lỗi parse JSON. Với prototype hay service nhỏ thì ổn. Với production app phức tạp thì bắt đầu đau đầu.
Socket.io: WebSocket với pin kèm theo
Socket.io không phải là WebSocket. Quan trọng phải hiểu điều này.
Socket.io là một thư viện abstraction layer xây trên WebSocket (và fallback về long polling nếu WS không khả dụng). Nó tự xử lý reconnection, namespace, rooms, broadcasting, acknowledgment - mọi thứ bạn sẽ phải tự viết khi dùng WebSocket thuần.
Dựng server với Socket.io:
Rooms trong Socket.io: mỗi room là một kênh broadcast riêng biệt
Phía client:
So sánh nhanh để quyết định dùng cái nào:
| Tiêu chí | WebSocket thuần | Socket.io |
|---|---|---|
| Bundle size | ~0KB | ~45KB client |
| Tự động reconnect | ❌ Tự làm | ✅ Có sẵn |
| Rooms/Namespaces | ❌ Tự làm | ✅ Có sẵn |
| Fallback transport | ❌ Không | ✅ Long polling |
| Debug tools | ❌ Cơ bản | ✅ Admin UI |
| Phù hợp | Service đơn giản | App phức tạp |
Nếu bạn đang build chat app, collaborative tool hay game web - chọn Socket.io. Tiết kiệm ít nhất vài ngày tự xây infrastructure.
Ba use case thực tế: chat, live dashboard, game
Chat app
Chat là use case kinh điển. Mình sẽ chỉ phần hay bị sai: typing indicator và read receipts.
Live dashboard
Dashboard realtime thường hiển thị metrics: đơn hàng mới, số người online, doanh thu theo giờ. Pattern hay dùng là server-push interval:
Lỗi hay gặp: quên clearInterval khi disconnect. Sau vài giờ server của bạn sẽ có vài trăm interval chạy âm thầm cho những client đã rời từ lâu.
Dọn dẹp interval khi disconnect - bước hay bị bỏ sót nhất
Game web đơn giản (multiplayer)
Game cần cập nhật vị trí player theo thời gian thực:
Game thực tế cần thêm server-side validation để chống gian lận, nhưng pattern cơ bản là vậy. Emit vị trí từ client → server broadcast đến room.
Reconnection và các lỗi triển khai phổ biến
Reconnection là thứ phân biệt prototype với production app.
Socket.io có auto-reconnect mặc định với exponential backoff. Nhưng bạn vẫn cần xử lý state recovery sau khi kết nối lại - người dùng cần biết họ đã miss gì trong thời gian offline.
Exponential backoff: tránh hammer server khi có sự cố
5 lỗi phổ biến nhất khi deploy realtime app:
-
Quên CORS cho WebSocket - Socket.io cần cấu hình cors riêng, không inherit từ Express middleware thông thường.
-
Load balancer không sticky sessions - Nếu bạn chạy nhiều server instance, client có thể reconnect vào instance khác và mất room state. Giải pháp: dùng Redis adapter cho Socket.io hoặc bật sticky sessions ở load balancer.
-
Memory leak từ event listener - Trong React, nếu bạn attach listener trong
useEffectmà không cleanup:
-
Gửi quá nhiều event - Game loop 60fps × 100 players = 6000 events/giây. Cân nhắc throttle hoặc chỉ gửi khi state thay đổi.
-
Không validate dữ liệu server-side - Mọi
socket.ontrên server phải validate input. Client có thể gửi bất cứ thứ gì.
Khi scale lên nhiều server, Redis adapter là bắt buộc:
Với Redis adapter, io.to(roomId).emit() hoạt động đúng dù client kết nối vào bất kỳ server instance nào.
Demo thực tế và tài nguyên để bắt đầu
Nếu muốn có playground để thử ngay, mình đã chuẩn bị một demo chat đơn giản bằng Socket.io:
Tải demo chat Socket.io (HTML)
Demo này bao gồm: tạo/join room bằng ID, gửi/nhận tin nhắn realtime, typing indicator, và hiển thị số người trong room. Đủ để bạn xem code structure trước khi bắt đầu dự án của mình.
Bắt đầu nhỏ, thêm feature khi cần - đừng over-engineer từ đầu
Về học thêm: nền tảng JavaScript vững là điều kiện tiên quyết trước khi vào WebSocket. Khóa Lập Trình JavaScript Nâng Cao của F8 cover các concept như event loop, closure và async/await - tất cả đều liên quan trực tiếp khi bạn xử lý WebSocket events. Còn nếu muốn build backend cho realtime app, khóa Node & ExpressJS là điểm xuất phát tốt nhất.
Tài liệu chính thức:
- Socket.io docs: Socket.io Docs
- MDN WebSocket API: {{resource:2}}
Giờ bạn đã có đủ nền tảng. Bước tiếp:
- Dựng thử chat app cơ bản theo code phần trước
- Thêm rooms và typing indicator
- Deploy lên Railway hoặc Render, test với 2 browser khác nhau
Gặp lỗi CORS kỳ lạ hay client không nhận được event? Drop comment mình xem qua.
