WebSocket Mobile Architecture
Bối cảnh
Ứng dụng mobile (Android/iOS) hiển thị trạng thái real-time của hàng nghìn IoT device. State thay đổi liên tục, yêu cầu kết nối WS ổn định khi app foreground.
Nguyên tắc thiết kế
- Không giữ WS khi background — mobile platform không hỗ trợ đáng tin cậy, không cần thiết vì không có user nhìn vào
- Background gap là acceptable — chỉ cần recover nhanh khi app resume
- Proactive health check thay vì reactive event —
onDone/onError/AppLifecyclekhông reliable trên cả Android và iOS - Snapshot + delta — khi reconnect lấy full state 1 lần, sau đó WS chỉ push thay đổi
Architecture tổng thể
┌─────────────────────────────────────────────┐
│ Mobile App │
│ │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ Ping │ │ WS Manager │ │
│ │ Scheduler │─────▶│ - connect() │ │
│ │ (every 3s) │ │ - disconnect() │ │
│ └─────────────┘ │ - send() │ │
│ │ └────────┬─────────┘ │
│ │ │ │
│ ┌──────▼──────┐ ┌────────▼─────────┐ │
│ │ Health │ │ Message Handler │ │
│ │ Monitor │ │ - pong → update │ │
│ │ lastPongAt │ │ lastPongAt │ │
│ └──────┬──────┘ │ - data → UI │ │
│ │ └──────────────────┘ │
│ │ dead? │
│ ▼ │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ Recovery │─────▶│ Snapshot API │ │
│ │ Flow │ │ (REST) │ │
│ └─────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────┘
│ WS
▼
┌─────────────────────────────────────────────┐
│ WS Gateway (Server) │
│ - Handle ping/pong │
│ - Push state delta │
└─────────────────────────────────────────────┘
Ping/Pong Health Check
Cơ chế
t=0s → Send PING, lastPongAt = T-3s → OK (gap = 3s < threshold 6s)
t=3s → Send PING, lastPongAt = T-3s → OK
t=6s → Send PING, lastPongAt = T-6s → DEAD (gap = 6s ≥ threshold)
→ trigger recovery flow
Thông số
| Parameter | Giá trị | Lý do |
|---|---|---|
| Ping interval | 3s | Đủ nhạy, không tốn battery |
| Pong threshold | 6s | = 2× interval, tránh false positive |
| Reconnect delay | 1s | Nhường network stack ổn định trước |
PING message format
// Client → Server
{ "type": "ping", "ts": 1718000000000 }
// Server → Client
{ "type": "pong", "ts": 1718000000000 }
Dùng application-level ping/pong thay vì WS protocol frame để dễ debug và đồng nhất giữa các platform.
Recovery Flow
Detect dead (lastPongAt gap > threshold)
│
├─ 1. Set isReconnecting = true
├─ 2. Pause ping scheduler
├─ 3. Show stale indicator trên UI
│
├─ 4. Gọi REST GET /api/devices/snapshot
│ └─ Nhận full current state của tất cả device
│
├─ 5. Update UI với snapshot data
│
├─ 6. Reconnect WS (exponential backoff nếu fail)
│
├─ 7. Resume ping scheduler
├─ 8. Set isReconnecting = false
└─ 9. Clear stale indicator
Exponential backoff khi reconnect thất bại
Attempt 1 → wait 1s
Attempt 2 → wait 2s
Attempt 3 → wait 4s
Attempt 4 → wait 8s
...
Max → wait 60s
State Management
Biến trạng thái cần track
lastPongAt : DateTime? // timestamp pong cuối nhận được
isReconnecting : bool // guard tránh double-trigger
isStale : bool // UI indicator
retryCount : int // cho exponential backoff
Guard isReconnecting
Nhiều nguồn có thể trigger recovery cùng lúc (onDone, onError, ping timeout). Dùng isReconnecting để đảm bảo chỉ 1 flow chạy tại một thời điểm:
if (isReconnecting) return // bỏ qua, đang xử lý rồi
isReconnecting = true
... recovery flow ...
isReconnecting = false
Background Behavior
| Trạng thái app | WS | Ping scheduler | Hành động khi resume |
|---|---|---|---|
| Foreground | Connected | Running | Bình thường |
| Background | Có thể die | Paused/killed | Check health → recover nếu cần |
| Resume | Unknown | Restart | Chờ ping cycle kế tiếp detect |
Không cố giữ WS sống khi background. Ping scheduler sẽ detect và recover khi app foreground trở lại.
Snapshot API
Endpoint phục vụ recovery, cần đảm bảo:
- Cache ở server (Redis, TTL ~2-3s) — tránh overload khi nhiều client reconnect cùng lúc (mất điện, restart app hàng loạt)
- Trả về trạng thái hiện tại của toàn bộ device, không phải event history
- Response nhanh < 500ms — user đang chờ nhìn thấy data
GET /api/devices/snapshot
Response: { "ts": 1718000000000, "devices": [ { "id": "...", "state": "..." }, ... ] }
UX Guidelines
- Hiển thị "Đang cập nhật..." ngay khi detect dead — đừng để user nhìn data cũ mà không biết
- Sau khi có snapshot → remove indicator, WS tiếp quản delta updates
- Không hiển thị error popup cho mỗi lần reconnect — chỉ show indicator nhẹ
- Nếu reconnect thất bại sau N lần → show lỗi rõ ràng hơn kèm nút retry thủ công
Không dùng Push Notification cho case này
Push Notification (FCM/APNs) không phù hợp vì:
- Rate limit: FCM ~600 msg/device/day, APNs còn thấp hơn
- Delivery delay: 1-5 giây, không acceptable cho real-time state
- Hàng nghìn device × liên tục thay đổi = bị throttle ngay lập tức
- Không đảm bảo thứ tự
Push Notification chỉ dùng cho critical alert (device offline đột ngột, vượt ngưỡng cảnh báo) — số lượng ít, không liên tục.