Các phương thức truyền nhận dữ liệu Real-Time: Từ Polling đến WebSocket & gRPC

Trong phát triển phần mềm hiện đại (Web, Mobile, Desktop), giao thức Request – Response truyền thống không còn đáp ứng đủ các tác vụ cần cập nhật dữ liệu ngay lập tức như ứng dụng Chat, Vị trí xe (Grab/Uber), Bảng giá Crypto hay Stream AI.
Để giải quyết bài toán này, các kỹ sư sử dụng nhiều phương thức truyền nhận dữ liệu thời gian thực (Real-time Communication) khác nhau. Chúng ta sẽ cùng đi vào tìm hiểu lý thuyết, ưu nhược điểm, ứng dụng và code minh họa của từng giải pháp.
Các phương thức truyền nhận Real-time phổ biến
HTTP Short Polling & Long Polling
HTTP Short Polling
Client gửi HTTP Request liên tục theo một chu kỳ cố định (ví dụ: x giây/lần) để kiểm tra dữ liệu mới từ Server.
- Ưu điểm: Cực kỳ đơn giản, dễ triển khai.
- Nhược điểm: Tốn băng thông và tài nguyên do nhiều request rỗng trả về không có dữ liệu.
- Ứng dụng: Kiểm tra trạng thái tiến trình nền (Upload video/file dung lượng lớn), hệ thống kiểm tra trạng thái đơn hàng đơn giản, đếm số lượt xem (view count) không yêu cầu thời gian thực tuyệt đối.
// Gửi request hỏi dữ liệu mỗi 3 giây setInterval(async () => { const response = await fetch('/api/check-status'); const data = await response.json(); console.log('Trạng thái mới:', data); }, 3000);
HTTP Long Polling
Client gửi request, nhưng Server không trả về ngay. Server sẽ giữ (hold) kết nối chờ đến khi có dữ liệu mới hoặc timeout mới phản hồi. Ngay khi nhận xong, Client lại mở một request mới.
- Ưu điểm: Giảm bớt số lượng request rỗng so với Short Polling.
- Nhược điểm: Vẫn dính overhead của HTTP Header cho mỗi lần kết nối lại; tốn tài nguyên quản lý kết nối treo ở Server.
- Ứng dụng: Hệ thống thông báo (Notifications) trên các nền tảng web cũ, ứng dụng Chat đơn giản, tính năng hỗ trợ trực tuyến (Live Chat widget) không muốn duy trì kết nối WebSocket thường trực.
async function pollData() {
try {
const response = await fetch('/api/long-poll');
const data = await response.json();
console.log('Nhận dữ liệu:', data);
} catch (err) {
console.error('Lỗi kết nối:', err);
} finally {
pollData(); // Tự động gọi lại request mới
}
}
pollData();
Server-Sent Events (SSE)
SSE cung cấp kênh truyền dữ liệu 1 chiều (Unidirectional) từ Server xuống Client thông qua kết nối HTTP kéo dài.
- Ưu điểm: Tự động kết nối lại (auto-reconnect), tích hợp hoàn hảo với HTTP/2, tốn ít tài nguyên hơn WebSocket nếu chỉ cần tải dữ liệu xuống.
- Nhược điểm: Chỉ truyền 1 chiều (Server –> Client) và chủ yếu hỗ trợ định dạng Text/UTF-8.
- Ứng dụng: Stream câu trả lời AI (ChatGPT), Bảng giá Crypto, Notification System.
Ví dụ:
- Server (Node.js/Express):
const express = require('express');
const app = express();
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const intervalId = setInterval(() => {
res.write(`data: ${JSON.stringify({ time: new Date().toLocaleTimeString() })}\n\n`);
}, 2000);
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
});
app.listen(3000);
- Client (Browser JS):
const eventSource = new EventSource('/events');
eventSource.onmessage = (event) => {
console.log('Dữ liệu từ Server:', JSON.parse(event.data));
};
WebSocket
WebSocket cung cấp một kênh giao tiếp 2 chiều (Bidirectional / Full-duplex) song công trên một kết nối TCP duy nhất sau bước bắt tay (Handshake) nâng cấp giao thức (ws:// hoặc wss://).
- Ưu điểm: Độ trễ cực thấp, giao tiếp 2 chiều độc lập linh hoạt.
- Nhược điểm: Phức tạp trong việc quản lý state kết nối, mở rộng (scaling) và Cân bằng tải (Load Balancing).
- Ứng dụng: Ứng dụng Chat, Game Multiplayer, công cụ chỉnh sửa nhóm (Google Docs), cập nhật vị trí thời gian thực.
Ví dụ:
- Server (Node.js
wslibrary):
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (message) => {
console.log(`Client gửi: ${message}`);
ws.send(`Server phản hồi: ${message}`);
});
});
- Client (Browser / Cross-platform JS):
const socket = new WebSocket('ws://localhost:8080');
socket.onopen = () => socket.send('Hello Server!');
socket.onmessage = (event) => console.log('Tin nhắn:', event.data);
Các phương thức thế hệ mới & Nâng cao
WebTransport
WebTransport là giao thức tiên tiến chạy trên nền HTTP/3 (UDP), cung cấp cơ chế kết nối 2 chiều giữa Client và Server với độ trễ siêu thấp.
- Ưu điểm:
- Khắc phục triệt để vấn đề Head-of-line blocking của TCP (nếu 1 gói tin bị mất, toàn bộ luồng dữ liệu không bị nghẽn).
- Hỗ trợ cả cơ chế gửi dữ liệu tin cậy (Reliable Streams) và không tin cậy (Unreliable Datagrams) trên cùng một kết nối.
- Tận dụng tính năng bảo mật và mã hóa hiện đại của HTTP/3 (QUIC).
- Nhược điểm:
- Chưa tương thích đầy đủ trên tất cả các trình duyệt cũ.
- Cấu hình Hạ tầng & Server hỗ trợ HTTP/3 còn khá phức tạp.
- Ứng dụng: Game Online Multiplayer đồ họa cao, Video Streaming độ trễ cực thấp (Live Webcasting), truyền dữ liệu cảm biến IoT.
Ví dụ WebTransport (Browser Client JS):
async function initWebTransport() {
const url = 'https://example.com:4433/webtransport';
const transport = new WebTransport(url);
// Đợi kết nối hoàn tất
await transport.ready;
console.log('Đã kết nối WebTransport via HTTP/3!');
// Gửi Datagram (Unreliable data - phù hợp cho Game/Stream)
const writer = transport.datagrams.writable.getWriter();
const data = new TextEncoder().encode('Dữ liệu tọa độ player');
await writer.write(data);
// Đọc dữ liệu từ Server
const reader = transport.datagrams.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log('Nhận datagram:', new TextDecoder().decode(value));
}
}
gRPC Streaming
gRPC là framework mã nguồn mở do Google phát triển, chạy trên nền HTTP/2 và sử dụng Protocol Buffers (Protobuf) để đóng gói dữ liệu nhị phân.
- Ưu điểm:
- Hiệu năng vượt trội: Dữ liệu dạng Binary (nhị phân) giúp kích thước gói tin nhỏ hơn nhiều so với JSON/Text, tốc độ đóng gói (serialize) cực nhanh.
- Đa dạng luồng truyền: Hỗ trợ 4 mô hình (Unary, Server Streaming, Client Streaming và Bidirectional Streaming).
- Strict Typing: Định nghĩa Schema rõ ràng qua file
.protogiúp giảm thiểu lỗi kết nối giữa Client – Server.
- Nhược điểm:
- Khó tương thích trực tiếp trên Web Browser truyền thống (thường phải thông qua proxy
gRPC-Web). - Dữ liệu nhị phân khiến việc Debug/Log trực tiếp bằng mắt thường khó khăn hơn JSON.
- Khó tương thích trực tiếp trên Web Browser truyền thống (thường phải thông qua proxy
- Ứng dụng: Kiến trúc Microservices (Backend-to-Backend), ứng dụng Mobile (Flutter/Native iOS-Android) kết nối với hệ thống Backend, các hệ thống tài chính/giao dịch yêu cầu xử lý lượng dữ liệu cực lớn.
Ví dụ gRPC Streaming (Định nghĩa Schema .proto & Server Node.js):
- Định nghĩa Schema (
chat.proto):
syntax = "proto3";
service ChatService {
// Bidirectional Streaming RPC
rpc ChatStream (stream ChatMessage) returns (stream ChatMessage);
}
message ChatMessage {
string user = 1;
string text = 2;
}
- Server (Node.js với
@grpc/grpc-js):
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync('chat.proto');
const proto = grpc.loadPackageDefinition(packageDefinition);
function chatStream(call) {
// Nhận dữ liệu stream từ Client
call.on('data', (message) => {
console.log(`[${message.user}]: ${message.text}`);
// Phản hồi lại stream cho Client
call.write({
user: 'Server',
text: `Đã nhận tin nhắn từ ${message.user}`
});
});
call.on('end', () => call.end());
}
const server = new grpc.Server();
server.addService(proto.ChatService.service, { ChatStream: chatStream });
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
server.start();
console.log('gRPC Server running on port 50051');
});
Bảng so sánh tổng quan
| Tiêu chí | Short Polling | Long Polling | SSE | WebSocket | WebTransport | gRPC Streaming |
| Hướng truyền | 2 chiều (Độc lập) | 2 chiều (Độc lập) | 1 chiều (Server –> Client) | 2 chiều (Song công) | 2 chiều (Song công) | Linh hoạt (1 chiều / 2 chiều) |
| Giao thức base | HTTP/1.1 | HTTP/1.1 | HTTP/1.1 / HTTP/2 | TCP (WS Protocol) | UDP (HTTP/3) | HTTP/2 (Protobuf) |
| Độ trễ | Cao | Trung bình | Thấp | Cực thấp | Siêu thấp | Cực thấp |
| Header Overhead | Rất cao | Cao | Rất thấp | Rất thấp | Rất thấp | Cực thấp (Binary payload) |
| Auto Reconnect | Thủ công | Thủ công | Tự động (Built-in) | Thủ công | Thủ công | Hỗ trợ qua gRPC SDK |
Lưu ý triển khai trên Web & Mobile
Trên nền tảng Web Browser
- Giới hạn số lượng kết nối (Connection Limit): Các trình duyệt (Chrome, Firefox, Safari) giới hạn số kết nối HTTP/1.1 tối đa đến cùng một domain (thường là 6 kết nối). Việc mở nhiều tab chứa SSE hoặc Long Polling có thể làm nghẽn toàn bộ các request khác đến Server. (Khắc phục: Dùng HTTP/2 hoặc dùng
SharedWorkerđể dùng chung 1 kết nối WebSocket/SSE giữa các tab). - Xử lý ngắt kết nối & Reconnect: Trình duyệt có thể mất mạng ngẫu nhiên hoặc tab bị rơi vào trạng thái “Sleep” khi chuyển tab. Hệ thống Client cần có cơ chế Exponential Backoff (thử lại sau 2s, 4s, 8s…) khi tự động kết nối lại để tránh gây nghẽn Server (Thổn thức kết nối/Connection Storm).
- Cross-Origin Resource Sharing (CORS): Các kết nối SSE hoặc Long Polling gọi sang domain khác cần được cấu hình Header CORS chính xác phía Server.
Trên nền tảng Mobile App (iOS / Android)
- Quản lý Năng lượng: Việc duy trì kết nối WebSocket/SSE liên tục khi app chạy ngầm sẽ tiêu tốn pin rất nhanh.
- Background Status: Hệ điều hành iOS/Android thường sẽ đóng băng (freeze) hoặc ngắt kết nối mạng khi ứng dụng bị đẩy vào background.
- Giải pháp kết hợp: Khi ứng dụng đóng/chạy ngầm, chuyển sang dùng Push Notification (FCM cho Android / APNs cho iOS). Khi người dùng mở lại app (Foreground), mới kích hoạt lại WebSocket/SSE để cập nhật dữ liệu trực tiếp.
Lời kết
Mỗi phương thức Real-time đều được thiết kế cho các bài toán riêng biệt. Việc chọn giải pháp nào không phụ thuộc vào phương thức đó “mới hay cũ”, mà nằm ở bản chất bài toán (1 chiều hay 2 chiều), hạ tầng Server, cũng như nền tảng thiết bị (Web hay Mobile) mà bạn đang hướng tới.


