SSE (Server-Sent Events) là một tiêu chuẩn W3C cho phép máy chủ gửi dữ liệu trực tiếp đến máy khách qua một kết nối HTTP duy nhất ở chế độ một chiều. Không giống WebSocket, SSE hoạt động trên HTTP thông thường và không yêu cầu giao thức đặc biệt hoặc thư viện ở phía máy khách. Theo thông số kỹ thuật W3C HTML Living Standard (2025), EventSource API được hỗ trợ trong tất cả các trình duyệt hiện đại, bao gồm Chrome, Firefox, Safari và Edge.
Những điểm chính
SSE (Server-Sent Events) là một công nghệ cho phép máy chủ web gửi dữ liệu đến máy khách bất cứ lúc nào sau khi thiết lập kết nối. Nó được WHATWG chuẩn hóa như một phần của HTML Living Standard và sử dụng kiểu MIME text/event-stream. SSE hỗ trợ truyền dữ liệu văn bản với khả năng chỉ định mã nhận dạng tin nhắn, loại sự kiện và độ trễ kết nối lại.
Không giống WebSocket, yêu cầu giao thức hai chiều và yêu cầu nâng cấp, SSE hoạt động trên HTTP thông thường. Máy chủ đặt tiêu đề Content-Type: text/event-stream, gửi dữ liệu theo từng đoạn và giữ kết nối mở. Máy khách nhận dữ liệu qua EventSource API của trình duyệt, tự động phân tích luồng và tạo sự kiện.
Theo CanIUse (2025), EventSource API được hỗ trợ trong 97,5% trình duyệt trên toàn cầu. Nó không được hỗ trợ trong Internet Explorer và một số trình duyệt di động (Samsung Internet trước phiên bản 7.0). Đối với những trường hợp này, có các polyfill mô phỏng EventSource qua XHR streaming. SSE không hoạt động với HTTP/1.1 pipelining, nhưng tương thích hoàn toàn với HTTP/2 server push.
SSE được đề xuất như một phần của thông số kỹ thuật HTML5 vào năm 2009 dưới tên Server-Sent DOM Events. Việc triển khai đầu tiên xuất hiện trong Opera 9.0, sau đó là Firefox 6.0 (2011), Chrome 9.0 (2011) và Safari 5.0 (2010). Năm 2015, thông số kỹ thuật đã được chuyển sang một phần riêng của HTML Living Standard. Bất chấp lịch sử hơn một thập kỷ, SSE vẫn ít phổ biến hơn WebSocket do bản chất một chiều của nó.
Cơ chế hoạt động của SSE như sau: máy khách tạo một thể hiện EventSource với URL của điểm cuối máy chủ. Trình duyệt gửi yêu cầu GET với tiêu đề Accept: text/event-stream. Máy chủ phản hồi với trạng thái 200 OK và tiêu đề Content-Type: text/event-stream, sau đó bắt đầu gửi dữ liệu ở định dạng event-stream. Kết nối vẫn mở cho đến khi máy chủ gửi tín hiệu kết thúc hoặc máy khách gọi close().
Ở phía máy chủ, dữ liệu được gửi theo từng đoạn (chunked transfer encoding). Mỗi đoạn dữ liệu là một tin nhắn văn bản bao gồm các dòng trường (event, data, id, retry). Máy chủ có thể gửi tin nhắn bất cứ lúc nào, làm cho SSE trở nên lý tưởng cho thông báo và cập nhật trạng thái. Kết nối không yêu cầu trao đổi gói tin heartbeat liên tục (như WebSocket), mặc dù trường retry kiểm soát tần suất kết nối lại.
Theo kiểm tra hiệu suất (2024), SSE cung cấp thông lượng lên đến 10.000 tin nhắn mỗi giây cho mỗi kết nối với kích thước tin nhắn 256 byte. Ở phía máy chủ, mỗi kết nối SSE tiêu thụ khoảng 5–10 KB bộ nhớ, cho phép một máy chủ hỗ trợ hơn 50.000 kết nối đồng thời với 1 GB RAM. Con số này thấp hơn đáng kể so với WebSocket do không có giao thức nhị phân.
Định dạng text/event-stream là một giao thức văn bản đơn giản, trong đó mỗi tin nhắn bao gồm các trường có tên được phân tách bằng ký tự xuống dòng. Mỗi trường có định dạng “Tên trường: giá trị”. Các tin nhắn được phân tách bằng hai ký tự xuống dòng (\n\n).
Các trường được hỗ trợ: event (loại sự kiện, mặc định là message), data (chuỗi dữ liệu, có thể nhiều dòng), id (mã nhận dạng sự kiện cuối cùng, được lưu trong Last-Event-ID), retry (thời gian kết nối lại tính bằng mili giây). Nhận xét bắt đầu bằng dấu hai chấm (:) và bị trình phân tích bỏ qua, nhưng có thể được sử dụng cho heartbeat.
| Trường | Bắt buộc | Mục đích |
|---|---|---|
| event | Không | Loại sự kiện (mặc định là message) |
| data | Có | Chuỗi dữ liệu tin nhắn |
| id | Không | Mã nhận dạng sự kiện cho Last-Event-ID |
| retry | Không | Độ trễ kết nối lại tính bằng ms |
: heartbeat comment
event: update
data: {"user": "Alice", "action": "typing"}
id: 1001
event: notification
data: {"type": "info", "text": "New version available"}
data: {"type": "action", "url": "/upgrade"}
retry: 3000
event: close
data: Session ended
EventSource API là giao diện trình duyệt tích hợp để nhận SSE. Để tạo kết nối, chỉ cần gọi hàm tạo với URL của điểm cuối. EventSource tự động thiết lập kết nối, xử lý kết nối lại và phân tích các tin nhắn đến thành các sự kiện JavaScript.
Các sự kiện EventSource: open (kết nối đã được thiết lập), message (đã nhận được tin nhắn mà không có sự kiện được chỉ định), error (lỗi kết nối). Đối với các sự kiện tùy chỉnh (event: custom), bạn có thể sử dụng addEventListener với tên sự kiện. EventSource tự động gửi tiêu đề Last-Event-ID khi kết nối lại, cho phép máy chủ tiếp tục luồng từ nơi bị gián đoạn.
Theo tài liệu MDN (2025), EventSource hỗ trợ CORS và truyền thông tin xác thực (withCredentials). EventSource không phù hợp để gửi tiêu đề tùy chỉnh hoặc phần thân yêu cầu — cần triển khai thủ công qua fetch + ReadableStream. EventSource không hỗ trợ dữ liệu nhị phân — chỉ văn bản và JSON.
const eventSource = new EventSource('/api/events/stream');
eventSource.addEventListener('open', () => {
console.log('Đã mở kết nối SSE');
});
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('Đã nhận:', data);
renderUpdate(data);
});
eventSource.addEventListener('notification', (event) => {
const notification = JSON.parse(event.data);
showNotification(notification.text);
});
eventSource.addEventListener('error', (error) => {
console.error('Lỗi SSE:', error);
// Trình duyệt tự động kết nối lại
});
// Đóng kết nối
eventSource.close();
SSE và WebSocket là hai công nghệ khác nhau cho giao tiếp thời gian thực, mỗi công nghệ có thế mạnh riêng. WebSocket phù hợp cho trao đổi dữ liệu hai chiều (trò chuyện, trò chơi, chỉnh sửa cộng tác), trong khi SSE dành cho luồng một chiều từ máy chủ đến máy khách (thông báo, nguồn cấp tin tức, bảng tín hiệu).
Sự khác biệt chính là WebSocket yêu cầu yêu cầu nâng cấp từ HTTP/1.1 lên giao thức WebSocket (ws://), có thể bị chặn bởi proxy doanh nghiệp. SSE hoạt động trên HTTP thông thường, đi qua bất kỳ proxy nào và không yêu cầu cấu hình đặc biệt cho máy chủ. SSE cũng dễ triển khai hơn — máy chủ không cần thư viện bổ sung, chỉ cần định dạng phản hồi HTTP một cách chính xác.
Theo kiểm tra so sánh (2024), trên một tiến trình máy chủ, SSE hỗ trợ nhiều hơn 30–50% kết nối so với WebSocket do giao thức đơn giản hơn. Tuy nhiên, SSE có độ trễ cao hơn (50–200 ms so với 10–50 ms của WebSocket) vì SSE sử dụng HTTP phân đoạn thay vì luồng hai chiều hoàn chỉnh với khung nhị phân.
| Đặc điểm | SSE | WebSocket |
|---|---|---|
| Hướng | Máy chủ → máy khách | Hai chiều |
| Giao thức | HTTP (text/event-stream) | ws:// / wss:// (RFC 6455) |
| Trình duyệt | 97,5% (EventSource tích hợp) | 97% (WebSocket tích hợp) |
| Dữ liệu | Chỉ văn bản / JSON | Văn bản + nhị phân (Blob, ArrayBuffer) |
| Xử lý proxy | Đi qua bất kỳ proxy nào | Yêu cầu cấu hình proxy |
| Kết nối lại | Tự động (trình duyệt) | Triển khai thủ công |
| Lịch sử | Last-Event-ID | Không có lịch sử tích hợp |
Triển khai SSE trên máy chủ không yêu cầu thư viện — chỉ cần đặt các tiêu đề HTTP chính xác và gửi dữ liệu ở định dạng text/event-stream. Hãy xem ví dụ trong Node.js sử dụng mô-đun http tích hợp. Máy chủ đặt các tiêu đề Content-Type và Cache-Control, sau đó gửi tin nhắn mỗi N giây.
Theo MDN Web Docs (2025), các tiêu đề bắt buộc cho SSE là: Content-Type: text/event-stream, Cache-Control: no-cache và Connection: keep-alive. Nếu không có Cache-Control, trình duyệt có thể lưu vùng đệm SSE stream, làm ngừng việc phân phối. Connection: keep-alive hướng dẫn trình duyệt giữ kết nối mở.
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
let eventId = 0;
const interval = setInterval(() => {
eventId++;
res.write(`id: ${eventId}\n`);
res.write(`event: update\n`);
res.write(`data: {"time": "${new Date().toISOString()}", "id":${eventId}}\n\n`);
}, 2000);
req.on('close', () => {
clearInterval(interval);
});
}).listen(3000);
from flask import Response, Flask
import time
import json
app = Flask(__name__)
@app.route('/stream')
def stream():
def generate():
event_id = 0
while True:
event_id += 1
data = json.dumps(
{'ticker': 'AAPL', 'price': 150.25})
yield f'id: {event_id}\nevent: price\ndata: {data}\n\n'
time.sleep(1)
return Response(generate(),
mimetype='text/event-stream')
Sử dụng SSE trong ứng dụng di động bị hạn chế do thiếu triển khai EventSource gốc cho iOS và Android. Trên các nền tảng di động, SSE được triển khai thông qua các thư viện bên thứ ba: trên iOS — qua URLSession với NSURLProtocol, trên Android — qua OkHttp với hỗ trợ SSE (okhttp-sse). Đối với React Native và Flutter, có các gói mô phỏng EventSource.
Trên iOS, triển khai SSE gốc có thể thực hiện qua URLSessionDataDelegate. Khi nhận dữ liệu trong phương thức urlSession(_:dataTask:didReceive:), ứng dụng tích lũy bộ đệm và phân tích định dạng event-stream thủ công. Theo blog phát triển iOS (2024), mức tiêu thụ pin với SSE trên iOS thấp hơn 40% so với kết nối WebSocket liên tục do không có gói tin heartbeat.
Trên Android, OkHttp cung cấp lớp EventSource.Factory để đăng ký các luồng SSE. Ứng dụng Android có thể sử dụng SSE cho thông báo khi FCM không khả dụng hoặc để đồng bộ dữ liệu trong nền. SSE trên Android hoạt động tốt với WorkManager cho các tác vụ nền kéo dài. Theo tài liệu OkHttp (2025), okhttp-sse hỗ trợ tự động kết nối lại với trình lắng nghe tùy chỉnh.
Câu hỏi thường gặp
SSE là truyền một chiều (máy chủ → máy khách) qua HTTP, không cần thư viện trên máy khách. WebSocket là truyền hai chiều với giao thức nhị phân. SSE dễ triển khai hơn, WebSocket phù hợp cho các tác vụ mà máy khách cũng gửi dữ liệu.
Không, SSE chỉ truyền dữ liệu văn bản. Đối với dữ liệu nhị phân (hình ảnh, âm thanh), cần mã hóa Base64, làm tăng kích thước lên 33%. Đối với luồng nhị phân, tốt hơn nên sử dụng WebSocket.
EventSource tự động kết nối lại khi xảy ra ngắt kết nối. Thời gian trễ được đặt bởi trường retry trong luồng (mặc định 1000 ms). Khi kết nối lại, trình duyệt gửi tiêu đề Last-Event-ID, cho phép máy chủ tiếp tục luồng từ nơi bị gián đoạn.
Mỗi trình duyệt có giới hạn về số lượng kết nối HTTP đồng thời đến một miền. Đối với HTTP/1.1 — 6–8 kết nối cho mỗi miền, đối với HTTP/2 — lên đến 100. SSE sử dụng một kết nối, do đó không có cạnh tranh với các yêu cầu khác.
SSE chỉ phù hợp để nhận tin nhắn (đến). Để gửi tin nhắn (đi), cần một yêu cầu HTTP riêng (POST). Đối với trò chuyện đầy đủ, thuận tiện hơn khi sử dụng WebSocket hoặc Socket.IO với giao tiếp hai chiều trong một kết nối duy nhất.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm