WebSocket: Truyền thông hai chiều thời gian thực
1. Tổng quan
WebSocket là giao thức truyền thông hai chiều song công (full-duplex) cho phép client và server trao đổi dữ liệu đồng thời trên một kết nối TCP duy nhất, được chuẩn hóa thành IETF RFC 6455 (2011). Chỉ kết nối ban đầu được thiết lập qua bắt tay HTTP; sau đó nó rời khỏi khuôn mẫu yêu cầu-phản hồi của HTTP và trao đổi thông điệp tự do trên một kết nối bền vững (persistent connection).
Lý do căn bản khiến WebSocket xuất hiện là vì mô hình yêu cầu-phản hồi một chiều, phi trạng thái (stateless) của HTTP không phù hợp về mặt cấu trúc với tương tác thời gian thực. Ở HTTP truyền thống, server chỉ có thể phản hồi khi client yêu cầu, nên không có cách nào để client nhận ngay các sự kiện phát sinh ở server (thông điệp mới, biến động giá, thông báo). Web thời kỳ đầu lách qua giới hạn này bằng thăm dò (polling), tức client định kỳ lặp lại câu hỏi "có gì mới không?", nhưng vì phần lớn yêu cầu kết thúc bằng phản hồi rỗng nên cách này lãng phí lớn tài nguyên mạng và server.
Các nỗ lực mô phỏng tính thời gian thực đã có trước WebSocket. Tiêu biểu là kỹ thuật "Comet" đổ một luồng vô hạn vào một iframe ẩn, hay long polling, nhưng chúng chỉ là những giải pháp chắp vá (workaround) lách quanh ràng buộc yêu cầu-phản hồi của HTTP. Do không có giao thức chuẩn hóa nên cách hiện thực mỗi nơi mỗi khác, và vấn đề tương thích với proxy, tường lửa cũng thường xuyên xảy ra. WebSocket là kết quả của việc chuẩn hóa chung bởi W3C (API) và IETF (giao thức) nhằm thay thế các giải pháp tạm bợ đó, và là một bước nhảy về chất ở chỗ nó mang lại cho trình duyệt một API WebSocket nhất quán và quy định một định dạng trên dây (wire format) rõ ràng để server và thiết bị trung gian tuân theo.
Khi các ứng dụng web giàu tính năng đòi hỏi server chủ động đẩy dữ liệu (server push) — như thao tác kéo trên Google Maps, chat, màn hình giao dịch chứng khoán — trở nên phổ biến vào cuối những năm 2000, sự kém hiệu quả của việc lặp lại gửi header HTTP (từ vài trăm byte đến vài KB) và mở kết nối TCP mới trên mỗi yêu cầu đã lộ ra như một điểm nghẽn nghiêm trọng. WebSocket được thiết kế đúng để nhắm vào điểm này, duy trì một kết nối đã mở để cả hai bên đều có thể nói trước. Việc chi phí phụ trội mỗi khung (frame) sau khi thiết lập kết nối chỉ tối thiểu 2 byte chính là cốt lõi tạo nên khoảng cách hiệu quả gấp hàng chục lần so với polling HTTP.
Đặc tính của WebSocket có thể tóm lại trong bốn điểm. Thứ nhất, tính hai chiều song công, nhờ đó server và client có thể gửi dữ liệu trước bất cứ lúc nào mà không chờ yêu cầu của bên kia. Thứ hai, kết nối bền vững, tái sử dụng kết nối TCP đã mở nên không lặp lại chi phí thiết lập/giải phóng kết nối và độ trễ bắt tay. Thứ ba, chi phí phụ trội thấp, truyền thông theo đơn vị khung nhỏ không có header HTTP để tiết kiệm băng thông. Thứ tư, hỗ trợ cả văn bản lẫn nhị phân, nên có thể trao đổi trực tiếp không chỉ thông điệp văn bản như JSON mà cả dữ liệu nhị phân như hình ảnh, âm thanh. Bốn đặc tính này kết hợp khiến WebSocket trở thành giao thức chuẩn web đáp ứng tự nhiên nhất yêu cầu "thời gian thực".
Dù vậy, phải nhận thức ngay từ đầu rằng những lợi ích này đi kèm chi phí. Kết nối bền vững có nghĩa là server phải giữ thường trú trong bộ nhớ lượng trạng thái kết nối bằng với số người kết nối, và tính hai chiều, cùng với sự linh hoạt rằng bên nào cũng có thể gửi thông điệp trước, đặt trách nhiệm kiểm soát luồng, xác thực và bảo mật lên ứng dụng. Nói cách khác, WebSocket là một sự đánh đổi "có được tính thời gian thực nhưng từ bỏ sự đơn giản mà HTTP phi trạng thái cho không", và việc phán đoán ngay ở giai đoạn thiết kế xem cuộc đánh đổi đó có đáng hay không là điều cốt lõi. Tài liệu này, dưới góc nhìn đó, lần lượt bàn từ cấu trúc giao thức đến mở rộng và bảo mật.
2. Bắt tay WebSocket và chuyển đổi giao thức
Thiết kế độc đáo nhất của WebSocket là nó bắt đầu trên nền HTTP rồi rời khỏi HTTP. Kết nối xuất phát bằng một yêu cầu HTTP GET bình thường nhưng yêu cầu chuyển đổi giao thức (protocol switching) qua header Upgrade: websocket. Nếu server chấp nhận, chính kết nối TCP đó được "nâng cấp" nguyên trạng thành kênh WebSocket, và từ đó HTTP hoàn toàn không can thiệp nữa. Sơ đồ tuần tự dưới đây thể hiện toàn bộ quá trình bắt tay.
sequenceDiagram
participant C as Client(Trinh duyet)
participant S as Server
Note over C,S: 1) Bắt tay HTTP Upgrade
C->>S: GET /chat HTTP/1.1<br/>Upgrade: websocket<br/>Sec-WebSocket-Key: (16B ngau nhien base64)
S->>C: HTTP/1.1 101 Switching Protocols<br/>Sec-WebSocket-Accept: (gia tri bam)
Note over C,S: 2) Nâng cấp cùng kết nối TCP lên WebSocket
C->>S: Khung van ban/nhi phan (bat ky luc nao)
S->>C: Khung van ban/nhi phan (bat ky luc nao)
Note over C,S: 3) Duy trì kết nối bằng Ping/Pong
C->>S: Khung Close (dong binh thuong)
Cốt lõi của bắt tay là cơ chế thách thức-đáp ứng của Sec-WebSocket-Key và Sec-WebSocket-Accept. Client mã hóa base64 một số ngẫu nhiên 16 byte và gửi làm Sec-WebSocket-Key; server nối thêm một chuỗi ma thuật cố định (258EAFA5-E914-47DA-95CA-C5AB0DC85B11), băm SHA-1 rồi mã hóa base64 và phản hồi giá trị đó làm Sec-WebSocket-Accept. Giá trị này không nhằm mục đích bảo mật mật mã mà là cơ chế kiểm chứng để ngăn một proxy bộ nhớ đệm trung gian nhầm bắt tay WebSocket là phản hồi HTTP thông thường rồi lưu đệm hoặc tái sử dụng. Chỉ khi mã phản hồi 101 Switching Protocols được trả về thì kết nối mới được nâng cấp.
Thiết kế xuất phát trên nền HTTP cũng mang lại lợi ích thực tiễn là đi xuyên tường lửa và proxy (traversal). Vì WebSocket dùng cùng cổng với HTTP — 80 (ws) và 443 (wss) — và bắt đầu bắt tay bằng yêu cầu HTTP chuẩn, nên tường lửa doanh nghiệp và proxy trung gian thường cho phép lưu lượng mà không cần mở cổng riêng. Đặc biệt wss:// được mã hóa TLS khiến thiết bị trung gian không thể nhìn vào payload, giảm can thiệp và chặn, nên trong thực tế wss:// được xem như giá trị mặc định trên thực tế, dù chỉ vì tính tương thích. Việc nó chạy nguyên trạng trên hạ tầng web, so với các giao thức thời gian thực khác dùng cổng chuyên dụng, là động lực ngầm cho sự lan rộng của WebSocket.
Ở giai đoạn bắt tay cũng diễn ra thương lượng giao thức con (subprotocol). Client mang trong header Sec-WebSocket-Protocol một danh sách các giao thức cấp ứng dụng mà nó hiểu (ví dụ: chat, wamp, mqtt), và khi server chọn một trong số đó rồi phản hồi thì cả hai bên chia sẻ cùng một quy ước thông điệp. Vì bản thân WebSocket chỉ là một ống dẫn chở thông điệp chứ không quy định ý nghĩa bên trong, nên trong thực tế người ta định nghĩa giao thức ứng dụng trên nền nó bằng một định dạng tuần tự hóa như JSON, MessagePack hay Protocol Buffers cùng với lược đồ thông điệp riêng. Sự tách tầng này là một lợi thế thiết kế cho phép cơ chế truyền tải và ngữ nghĩa thông điệp tiến hóa độc lập với nhau.
Sau khi nâng cấp, đơn vị truyền thông không phải là thông điệp HTTP mà là khung (frame). Khung gồm bit FIN (có phải mảnh cuối của thông điệp không), opcode (loại khung), bit mặt nạ (masking), độ dài payload, (với chiều client→server) khóa mặt nạ, và payload. Opcode chia thành khung dữ liệu (0x1 văn bản, 0x2 nhị phân) và khung điều khiển (0x8 Close, 0x9 Ping, 0xA Pong). Đặc biệt có một quy định bắt buộc rằng mọi khung client gửi phải được che mặt nạ XOR bằng một khóa mặt nạ 32 bit, một thiết kế nhằm ngăn tấn công đầu độc bộ nhớ đệm (cache poisoning) trong đó một kịch bản độc hại lặp lại gửi văn bản thuần dự đoán được để đầu độc bộ đệm của proxy trung gian. Bảng dưới đây tóm tắt các thành phần khung chính.
| Thành phần | Kích thước | Vai trò |
|---|---|---|
| FIN | 1 bit | Đánh dấu có phải khung cuối của thông điệp không (hỗ trợ phân mảnh) |
| Opcode | 4 bit | Loại khung (văn bản, nhị phân, Close, Ping, Pong) |
| Mask | 1 bit | Payload có được che mặt nạ không (bắt buộc với chiều client→server) |
| Payload len | 7/16/64 bit | Mã hóa độ dài biến thiên giảm thiểu chi phí phụ trội cho thông điệp ngắn |
| Masking-key | 0/4 byte | Khóa 32 bit dùng khi che mặt nạ |
| Payload | Biến thiên | Dữ liệu thực (văn bản UTF-8 hoặc nhị phân) |
Một khái niệm quan trọng khác trong thiết kế khung là phân mảnh (fragmentation). Một thông điệp logic có thể được chia thành nhiều khung để gửi: chỉ khung đầu mang opcode, các khung tiếp theo được đánh dấu continuation (0x0), và bit FIN được đặt bằng 1 ở khung cuối. Nhờ cấu trúc này, bên nhận có thể nhận và xử lý dữ liệu theo dòng (streaming) ngay cả khi chưa biết trước kích thước toàn thông điệp, điều này có lợi cho việc truyền tệp nhị phân lớn (tệp, phương tiện) và cho dữ liệu có độ dài biến thiên và được sinh dần dần, như phản hồi theo từng token của AI tạo sinh. Ngoài ra, khung điều khiển (Ping, Pong, Close) được phép chen vào giữa các khung dữ liệu, nên có thể kiểm tra trạng thái kết nối ngay cả trong lúc truyền khối lượng lớn.
Lý do tách khung điều khiển khỏi khung dữ liệu là để tách bạch các mối quan tâm giữa quản lý kết nối và truyền dữ liệu (separation of concerns). Nội dung thực mà ứng dụng trao đổi được chuyển qua khung dữ liệu văn bản/nhị phân, trong khi các tác vụ siêu dữ liệu như xác nhận kết nối còn sống và đóng kết nối một cách bình thường do khung điều khiển đảm nhận. Nhờ sự tách bạch này, logic ứng dụng có thể chỉ tập trung vào xử lý dữ liệu, còn việc duy trì và kết thúc kết nối được xử lý trong suốt ở tầng giao thức/thư viện.
Việc trường độ dài khung dùng mã hóa độ dài biến thiên cũng là cốt lõi của hiệu quả. Nếu payload từ 125 byte trở xuống thì một trường độ dài 7 bit là đủ; nếu lớn hơn thì dùng trường mở rộng 16 bit hoặc 64 bit. Nghĩa là một thông điệp ngắn như một dòng chat chỉ gắn một header cực nhỏ 2~6 byte, và trường độ dài chỉ dài ra khi truyền khối lượng lớn. Đây là điển hình của nguyên tắc thiết kế "làm cho trường hợp phổ biến rẻ nhất", với hiệu quả tiết kiệm băng thông tích lũy lớn trong dịch vụ thời gian thực nơi vô số thông điệp nhỏ qua lại.
3. Kiến trúc truyền thông và vòng đời kết nối
WebSocket khác biệt căn bản với HTTP phi trạng thái ở chỗ nó là kết nối có trạng thái (stateful), điều này ảnh hưởng trực tiếp đến thiết kế kiến trúc server. Sơ đồ khái niệm dưới đây thể hiện một kiến trúc điển hình bao gồm server WebSocket mà nhiều client kết nối tới và một message broker.
graph TB
subgraph Clients["Client"]
C1["Trinh duyet A"]
C2["Trinh duyet B"]
C3["Ung dung di dong"]
end
LB["Can bang tai<br/>(Sticky Session/L7)"]
subgraph Backend["Tang server WebSocket"]
W1["Server WS #1<br/>(giu trang thai ket noi)"]
W2["Server WS #2<br/>(giu trang thai ket noi)"]
end
MB["Message broker<br/>(Redis Pub/Sub, Kafka)"]
C1 -->|wss://| LB
C2 -->|wss://| LB
C3 -->|wss://| LB
LB --> W1
LB --> W2
W1 <--> MB
W2 <--> MB
Vòng đời kết nối trôi qua các giai đoạn Mở (Open) → Trao đổi thông điệp (Message) → Duy trì (Ping/Pong) → Đóng (Close). Theo góc nhìn API trình duyệt, sau khi tạo new WebSocket(url), mỗi giai đoạn được xử lý bằng các trình xử lý sự kiện onopen, onmessage, onerror, onclose. Vì là mô hình bất đồng bộ hướng sự kiện, ứng dụng phản ứng qua callback mà không cần thăm dò việc thông điệp đến, và việc gửi tức thì chỉ với một lần send(). Tuy nhiên, trong môi trường di động mạng không ổn định, kết nối hay bị rớt, nên một dịch vụ bền vững đòi hỏi chính client tự hiện thực logic phát hiện onclose và thử kết nối lại bằng backoff hàm mũ (exponential backoff), đồng bộ lại trạng thái đã thay đổi trong lúc mất kết nối. Vì gánh nặng kết nối lại/đồng bộ lại này, trong thực tế các thư viện như Socket.IO đã nói ở trên được ưa chuộng.
Một điểm thiết kế khác của giai đoạn Mở là ánh xạ kết nối với phiên (session). Server phải quản lý mỗi kết nối đã thiết lập bằng cách liên kết nó với các khái niệm miền như người dùng, kênh, phòng (room), thì mới có thể gửi (targeted push) thông điệp một cách chọn lọc chỉ tới một người dùng hoặc nhóm cụ thể. Chẳng hạn trong mô hình phòng chat, các kết nối được gom theo đơn vị phòng, và phát biểu của một người dùng chỉ được broadcast tới các kết nối của thành viên cùng phòng. Vì bảng ánh xạ này là trạng thái thường trú trong bộ nhớ server, nên khi mở rộng theo chiều ngang nó phải được chia sẻ và phối hợp giữa các server thông qua backplane của broker đã mô tả ở trên. Ở giai đoạn Mở, server giữ mỗi kết nối như một đối tượng phiên riêng trong bộ nhớ, nên số người kết nối trở thành số kết nối đồng thời (trạng thái) mà server phải duy trì. Để xử lý hàng chục nghìn đến hàng trăm nghìn kết nối đồng thời, cần một cấu trúc xử lý nhiều kết nối với ít luồng thông qua I/O bất đồng bộ hướng sự kiện (Node.js, Netty, goroutine của Go, v.v.).
Ở giai đoạn trao đổi thông điệp, bảo đảm thứ tự và áp lực ngược (backpressure) trở thành điểm cân nhắc thiết kế. Vì WebSocket hoạt động trên một kết nối TCP duy nhất nên các thông điệp trong cùng kết nối được bảo đảm thứ tự truyền, nhưng nếu bên nhận không theo kịp tốc độ xử lý thì bộ đệm gửi dồn lại, gây áp lực bộ nhớ và tích lũy độ trễ. Chẳng hạn trong dịch vụ giao dịch đẩy xuống hàng nghìn tick giá mỗi giây, nếu client chậm thì server phải bảo đảm ưu tiên dữ liệu mới nhất bằng chiến lược hợp nhất (conflation) hoặc bỏ các tick cũ. Đây là vấn đề kiểm soát luồng đặc thù của kết nối bền vững, vốn không có trong mô hình yêu cầu-phản hồi của HTTP.
Khung Ping/Pong của giai đoạn Duy trì đặc biệt quan trọng trong thực tế. Các thiết bị cân bằng tải, proxy, NAT trung gian coi một kết nối không có lưu lượng trong một khoảng thời gian là nhàn rỗi (idle) và tùy ý cắt nó (thường quanh 60 giây), nên để ngăn điều này cần định kỳ gửi Ping để báo rằng kết nối còn sống. Ngoài ra, khi kết nối TCP rớt bất thường (ví dụ chuyển mạng của thiết bị di động), ứng dụng không biết ngay, nên phải hiện thực logic nhịp tim (heartbeat) phán đoán là kết nối chết (dead connection) rồi dọn dẹp và khuyến khích kết nối lại nếu không có phản hồi Pong cho Ping trong một khoảng thời gian nhất định. Ở giai đoạn Đóng, một mã trạng thái (ví dụ 1000 đóng bình thường, 1006 đóng bất thường) được mang trong khung Close để cả hai bên đóng kết nối theo thỏa thuận. Đóng bình thường trải qua một bắt tay đóng (closing handshake) trong đó cả hai bên trao đổi khung Close, nhưng trong tình huống bất thường như mất mạng, TCP rớt mà không có trao đổi này, nên server phải phát hiện bằng hết thời gian nhịp tim rồi dọn dẹp.
Xét về vận hành, WebSocket là đối tượng mà khó bảo đảm tính quan sát được (observability). HTTP dễ giám sát vì mỗi yêu cầu để lại mã trạng thái và thời gian phản hồi trong log, nhưng kết nối WebSocket kéo dài lâu không có chỉ số theo từng yêu cầu, nên phải đo riêng các chỉ số lấy kết nối làm trung tâm như số kết nối đồng thời, phân bố thời lượng kết nối, thông lượng thông điệp, tần suất kết nối lại, tỷ lệ kết nối chết. Do đó trong thực tế người ta thu thập các sự kiện mở/đóng kết nối và độ trễ thông điệp làm chỉ số, và xây dựng bảng điều khiển coi sự gia tăng đột biến các mã đóng bất thường (1006, v.v.) là tín hiệu sự cố. Nếu thiếu thiết kế đo lường này thì khó bắt sự cố sớm trong dịch vụ thời gian thực quy mô lớn.
Mặt khác, với kết nối bền vững, cách sự cố lan truyền cũng khác HTTP. HTTP phục hồi tự nhiên bằng yêu cầu kế tiếp ngay cả khi một yêu cầu thất bại, nhưng nếu một server WebSocket sập thì hàng chục nghìn kết nối nó đang giữ rớt đồng thời, và một cơn lũ kết nối lại (thundering herd) khi các client đồng loạt thử kết nối lại có thể chuyển quá tải sang các server còn lại. Để giảm nhẹ điều này, nên trộn một độ trễ ngẫu nhiên (jitter) vào thời điểm kết nối lại để phân tán tải, và đặt ở phía server một giới hạn tốc độ thiết lập kết nối (admission control) để hấp thụ luồng đổ vào đột ngột.
4. So sánh với các kỹ thuật thời gian thực hiện có
Để hiểu đúng vị thế của WebSocket, cần nhìn thêm vì sao nó khác với các kỹ thuật tiền nhiệm và thay thế. Polling đơn giản nhất để hiện thực, nhưng ví dụ nếu mười nghìn client chat thăm dò theo chu kỳ một giây kết nối vào thì phát sinh mười nghìn yêu cầu rỗng mỗi giây, phần lớn trở thành lưu lượng vô nghĩa. Long Polling giảm phản hồi rỗng bằng cách để server giữ phản hồi cho tới khi có dữ liệu mới, nhưng chi phí thiết lập lại kết nối và gửi lại header HTTP trên mỗi phản hồi vẫn còn. SSE (Server-Sent Events) hiệu quả cho luồng một chiều server→client và có ưu điểm tự động kết nối lại cùng tính thân thiện HTTP/2, nhưng chiều client→server vẫn phụ thuộc vào một yêu cầu HTTP riêng. Bảng dưới đây so sánh đặc tính của bốn kỹ thuật.
| Phân loại | Polling | Long Polling | SSE | WebSocket |
|---|---|---|---|---|
| Hướng truyền thông | Một chiều (dựa trên yêu cầu) | Một chiều (dựa trên yêu cầu) | Một chiều server→client | Hai chiều (song công) |
| Duy trì kết nối | Kết nối mới mỗi lần | Kết nối lại mỗi phản hồi | Bền vững (một chiều) | Bền vững (hai chiều) |
| Tính thời gian thực | Thấp (phụ thuộc chu kỳ) | Trung bình | Cao | Rất cao |
| Chi phí phụ trội | Rất lớn (lặp header) | Lớn | Nhỏ | Tối thiểu (khung 2B+) |
| Định dạng dữ liệu | Không giới hạn | Không giới hạn | Văn bản (UTF-8) | Văn bản + nhị phân |
| Dùng tiêu biểu | Truy vấn trạng thái đơn giản | Thông báo | Newsfeed, bảng giá chứng khoán | Chat, game, cộng tác, giao dịch |
Cũng cần nhận rõ ranh giới với các công nghệ hai chiều khác lân cận WebSocket. gRPC luồng hai chiều dựa trên HTTP/2, cung cấp hợp đồng định kiểu mạnh (Protocol Buffers) và sinh mã, có thế mạnh trong truyền thông giữa dịch vụ (service-to-service), nhưng khó dùng trực tiếp ở trình duyệt và cần một proxy riêng (gRPC-Web). MQTT là giao thức IoT nhẹ theo mô hình xuất bản-đăng ký, tối ưu cho truyền thông cảm biến trên mạng công suất thấp, không ổn định, và trong môi trường web thường được kết hợp dưới dạng MQTT over WebSocket. Nói cách khác, WebSocket có vị thế rõ ràng nhất như một ống dẫn hai chiều đa dụng giữa trình duyệt và server, trong khi truyền thông lấy hợp đồng làm trung tâm giữa các dịch vụ phù hợp với gRPC, và thu thập thiết bị IoT quy mô lớn phù hợp với MQTT, mỗi thứ có chỗ đứng riêng. Dưới góc nhìn của Kỹ sư chuyên nghiệp, nhãn quan phân biệt ranh giới của các nhóm công nghệ chồng lấn theo bối cảnh yêu cầu để lựa chọn giữa chúng là điều quan trọng.
Bản chất của chỗ nảy sinh khác biệt nằm ở hai trục: "có duy trì kết nối hay không" và "ai có thể nói trước." Polling và long polling ở lại trong khuôn yêu cầu-phản hồi của HTTP, với client nắm thế chủ động, nên server push về căn bản là bất khả thi; WebSocket nâng cấp kết nối và rời khỏi chính khuôn đó. Hàm ý thực tiễn: với một thông báo 1:1 hay luồng một chiều, SSE là lựa chọn hợp lý hơn, đơn giản hơn và khớp tốt với hạ tầng HTTP, và chỉ khi truyền thông hai chiều độ trễ thấp là thiết yếu — như chat, game nhiều người chơi, chỉnh sửa cộng tác thời gian thực, giao dịch chứng khoán — thì WebSocket mới là câu trả lời đúng. Chẳng hạn các ứng dụng nhắn tin như Slack và KakaoTalk Web, bảng giá thời gian thực của Upbit và Binance, và chỉnh sửa cộng tác của Google Docs đều được hiện thực bằng công nghệ họ WebSocket. Đặc biệt, chỉnh sửa cộng tác đặt các thuật toán kiểm soát đồng thời như OT (Operational Transformation) hoặc CRDT lên trên WebSocket để các chỉnh sửa đồng thời của nhiều người dùng không xung đột, lan truyền thao tác chỉnh sửa của mỗi bên tới các bên khác theo thời gian thực và hội tụ chúng. Đây là một ví dụ tiêu biểu cho thấy WebSocket đảm nhận vai trò nền truyền tải (transport foundation) cho cộng tác thời gian thực phức tạp, vượt quá việc chuyển thông điệp đơn thuần. Dùng WebSocket một cách vô điều kiện có thể là thiết kế thừa chỉ làm phình chi phí giữ trạng thái và độ phức tạp vận hành.
Nhìn sự khác biệt hiệu quả bằng con số cụ thể sẽ làm rõ hàm ý của nó. Nếu 1.000 người thực hiện polling mang theo header HTTP trung bình 300 byte theo chu kỳ một giây, thì ngay cả khi không có thân thông điệp cũng phát sinh khoảng 300 KB lưu lượng header thuần mỗi giây, và server phải xử lý 1.000 lần thiết lập/giải phóng kết nối mỗi giây. Ngược lại, nếu chuyển cùng số người dùng đó sang WebSocket thì kết nối cố định ở 1.000 và chỉ một khung vài byte qua lại khi thực sự có sự kiện, nên lưu lượng mạng và tải tính toán của server sụt mạnh xuống mức phần trăm một chữ số. Chính khoảng cách này là lý do thực chất để các dịch vụ thời gian thực quy mô lớn chọn WebSocket; ngược lại, với dịch vụ có tần suất sự kiện thấp và yêu cầu thời gian thực lỏng lẻo thì lợi ích này không được cảm nhận, và polling đơn giản có thể kinh tế hơn.
Một hàm ý thực tiễn khác đáng chú ý là sự khác biệt về chất của độ trễ (latency). Với polling chu kỳ một giây, tồn tại về mặt cấu trúc một độ trễ trung bình 0,5 giây và tệ nhất 1 giây sau khi sự kiện xảy ra. Rút ngắn chu kỳ thì giảm độ trễ nhưng nảy sinh sự đánh đổi là yêu cầu rỗng tăng vọt. Ngược lại, WebSocket đẩy ngay khi sự kiện phát sinh, nên độ trễ hội tụ về mức thời gian khứ hồi mạng (vài đến vài chục ms). Trong các lĩnh vực mà độ trễ vài chục ms chi phối trải nghiệm người dùng và tính công bằng — như game nhiều người chơi và đấu giá thời gian thực — sự khác biệt về chất này trên thực tế quyết định lựa chọn công nghệ.
5. Chuyên sâu: Khả năng mở rộng, bảo mật và xu hướng mới nhất
Thách thức khó nhất khi áp dụng WebSocket vào một dịch vụ quy mô lớn thực tế là mở rộng theo chiều ngang của kết nối có trạng thái. Vì HTTP phi trạng thái nên server nào nhận yêu cầu cũng được, nhưng với WebSocket thì một server cụ thể giữ trạng thái của một kết nối cụ thể. Do đó nảy sinh tình huống server B phải gửi thông điệp cho một người dùng kết nối tới server A, và để giải quyết điều này cần một backplane message broker. Trong thực tế người ta đặt Redis Pub/Sub hoặc Kafka làm bus trung tâm, và mỗi server WebSocket định tuyến thông điệp qua các kết nối nó đang giữ. Ví dụ, với một dịch vụ quy mô một triệu kết nối đồng thời, một cấu hình điển hình đặt hàng chục server, mỗi server chứa 50.000–100.000 kết nối, và chuyển tiếp broadcast qua Redis Pub/Sub. Bộ cân bằng tải phải hiểu header Upgrade ở L7 và ghim cùng một kết nối vào cùng một server bằng sticky session.
Ước lượng dung lượng (capacity planning) cũng được tiếp cận khác với HTTP. Dịch vụ HTTP đo dung lượng bằng số yêu cầu mỗi giây (RPS) và thời gian phản hồi trung bình, nhưng với WebSocket thì số kết nối đồng thời chính là tài nguyên thường trú, nên tiêu thụ bộ nhớ và file descriptor trên mỗi kết nối trở thành ràng buộc hàng đầu. Ví dụ, nếu giữ vài chục KB bộ đệm mỗi kết nối thì chỉ riêng 100.000 kết nối đã giữ thường trú vài GB bộ nhớ. Do đó trước khi triển khai nên thực hiện một kiểm thử tải mô phỏng khối lượng kết nối lớn (ví dụ, đo điểm giới hạn bằng cách tăng dần kết nối với các client ảo) để cố định trước số kết nối an toàn mỗi server và đơn vị mở rộng theo chiều ngang. Nếu ước lượng này kém thì mọi thứ ổn trong thời bình nhưng một cơn tăng vọt lưu lượng kích hoạt sự cố dây chuyền do từ chối kết nối và cạn kiệt bộ nhớ.
Thiết kế xác thực có một ràng buộc đặc thù của WebSocket. Hàm khởi tạo WebSocket chuẩn của trình duyệt không thể đặt header HTTP tùy biến, nên không thể dùng nguyên cách truyền token xác thực qua header Authorization: Bearer <token> như REST API. Vì điều này, trong thực tế người ta mang một token ngắn hạn trong tham số truy vấn của URL bắt tay, hoặc dùng mẫu trao đổi thông tin xác thực trong thông điệp đầu tiên ngay sau khi thiết lập kết nối. Cách tham số truy vấn có rủi ro token còn lại trong log và lịch sử, nên dùng một token dùng một lần có thời hạn ngắn, còn cách xác thực bằng thông điệp sau kết nối đòi hỏi quản lý trạng thái chặn truyền dữ liệu cho tới khi xác thực hoàn tất. Như vậy, xác thực trên một kết nối có trạng thái đòi hỏi mẫu thiết kế khác với HTTP phi trạng thái.
Mối đe dọa cần cảnh giác nhất về mặt bảo mật là CSWSH (Cross-Site WebSocket Hijacking). Vì bắt tay WebSocket không nhận được sự bảo vệ đầy đủ từ Chính sách cùng nguồn gốc (SOP) của trình duyệt và cookie được đính kèm tự động, nên trang của kẻ tấn công có thể lén mở một WebSocket bằng cookie xác thực của nạn nhân. Do đó server phải luôn kiểm chứng header Origin khi bắt tay và đồng thời dùng xác thực dựa trên token không chỉ dựa vào cookie (ví dụ, truyền một token dùng một lần khi kết nối). Đoạn truyền tải cũng nên, như một nguyên tắc, dùng wss:// (cổng mặc định 443) được mã hóa TLS thay cho ws:// thuần, để chặn nghe lén và tấn công người đứng giữa. Giới hạn tốc độ (rate limiting) chống lũ thông điệp (flooding), mức trần kích thước thông điệp, và kiểm chứng đầu vào cũng phải được áp dụng cùng nhau ở tầng ứng dụng.
Thiết kế backplane đi kèm các sự đánh đổi. Redis Pub/Sub có độ trễ thấp và đơn giản để hiện thực, nhưng nó không lưu trữ thông điệp và có đặc tính fire-and-forget chỉ nhận các thông điệp sau thời điểm đăng ký, nên nếu cần bảo đảm phân phối thì phải dùng một broker lưu bền log như Kafka. Ngược lại, Kafka mạnh về độ bền và xử lý lại nhưng có độ trễ và độ phức tạp vận hành lớn hơn Redis. Do đó, điều cốt lõi là thiết kế broker và mức bảo đảm phân phối khác nhau theo yêu cầu nghiệp vụ như "không được phép mất thông điệp chat" hay "với trạng thái game chỉ giá trị mới nhất quan trọng." Ngoài ra, ở hàng trăm nghìn kết nối đồng thời, việc tinh chỉnh ở mức OS/hạ tầng như giới hạn file descriptor (ulimit), bộ đệm TCP nhân, và giới hạn kết nối của bộ cân bằng tải phải song hành thì dung lượng thực mới được bảo đảm.
Về xu hướng mới nhất, một vài dòng chảy nổi bật. Thứ nhất, WebSocket trong môi trường HTTP/2 và HTTP/3. RFC 8441 định nghĩa một cách bootstrap WebSocket trong một luồng HTTP/2 duy nhất (:protocol = websocket), và hơn nữa, WebTransport cung cấp cả luồng hai chiều tin cậy lẫn không tin cậy trên HTTP/3 (QUIC) và đang nổi lên như một công nghệ kế thừa WebSocket trong các lĩnh vực game và phương tiện cần độ trễ thấp và nhiều luồng. Trong khi WebSocket là một luồng đơn được bảo đảm thứ tự nên có giới hạn HOL Blocking mà độ trễ của một thông điệp chặn các thông điệp sau, thì WebTransport hỗ trợ nhiều luồng và datagram để nới lỏng ràng buộc này, nên hai thứ được dự kiến phát triển theo quan hệ bổ sung hoặc thay thế. Thứ hai, phần mở rộng nén permessage-deflate (RFC 7692) được dùng rộng rãi để tiết kiệm băng thông cho thông điệp văn bản. Tuy nhiên, nén tiêu thụ thêm tải CPU và bộ nhớ (ngữ cảnh nén), nên nếu thông điệp nhỏ và thường xuyên thì thực ra có thể là lỗ, và phải được áp dụng chọn lọc theo đặc tính lưu lượng. Thứ ba, các thư viện cấp cao như Socket.IO và SignalR, vốn cung cấp tự động kết nối lại và dự phòng (thay thế bằng polling) ngay cả khi kết nối rớt, được dùng gần như một chuẩn trên thực tế, bổ sung tính bền vững qua nhiều môi trường mạng đa dạng.
Cuối cùng, sự lan rộng của AI tạo sinh lại một lần nữa đẩy nhu cầu WebSocket lên cao. Khi UX trong đó một LLM sinh câu trả lời dần dần theo từng token và truyền dòng tới người dùng theo thời gian thực trở thành chuẩn, WebSocket (hoặc SSE) được dùng rộng rãi làm ống dẫn qua đó server đẩy ngay các token được sinh. Vì tính hai chiều cũng hữu ích để xử lý sự can thiệp giữa chừng của người dùng (dừng sinh, truy vấn bổ sung), nên tính phù hợp của WebSocket đang được soi lại trong các giao diện AI hội thoại và tác tử (agent).
6. Cân nhắc và hàm ý
- Chiến lược áp dụng (tính phù hợp của lựa chọn công nghệ): WebSocket không phải thuốc chữa bách bệnh. SSE đủ cho server push một chiều và polling cho truy vấn trạng thái đơn giản; chỉ nên chọn WebSocket khi tính hai chiều độ trễ thấp là yêu cầu cốt lõi. Cân nhắc đồng thời các yêu cầu chức năng (tính thời gian thực, tính định hướng) cùng các yêu cầu phi chức năng (quy mô kết nối đồng thời, năng lực vận hành) để tránh thiết kế thừa là vai trò của Kỹ sư chuyên nghiệp.
- Sự đánh đổi (hiệu quả so với độ phức tạp): WebSocket tối đa hóa hiệu quả truyền tải bằng cách giảm thiểu chi phí phụ trội khung, nhưng độ phức tạp vận hành và chi phí bộ nhớ tăng lớn so với HTTP do kết nối có trạng thái, sticky session, backplane broker, nhịp tim, v.v. Phải kiểm chứng bằng phân tích định lượng (số kết nối đồng thời, tần suất thông điệp, yêu cầu độ trễ) xem lợi ích hiệu quả có vượt chi phí này không.
- Góc nhìn bảo mật và quản trị: Với các đặc tính kết nối luôn bật và đính kèm cookie tự động, rủi ro CSWSH và vượt qua xác thực luôn hiện hữu, nên phải tích hợp sẵn kiểm chứng
Origin, xác thực token, mã hóawss://và giới hạn tốc độ như các kiểm soát chuẩn ngay ở giai đoạn thiết kế. Vì mã hóa luôn bật làm khó phát hiện mối đe dọa dựa trên mạng, một kiến trúc dịch trọng tâm kiểm soát sang tầng điểm cuối/ứng dụng (khớp với zero trust) phải song hành. - Tính sẵn sàng và ứng phó sự cố: Kết nối có trạng thái có đặc tính rớt đồng loạt khi khởi động lại server hay triển khai, nên phải rút cạn (drain) kết nối một cách dần dần khi triển khai không gián đoạn và bắt buộc hiện thực tự động kết nối lại cùng backoff ở phía client. Dọn kết nối chết, ngăn lũ kết nối lại (thundering herd) (áp dụng jitter) cũng là cốt lõi của thiết kế vận hành.
- Triển vọng và công nghệ liên kết: WebSocket có lộ trình tiến hóa mở qua bootstrap HTTP/2 (RFC 8441) và WebTransport dựa trên HTTP/3, và tầm quan trọng của nó được dự kiến tăng khi nhu cầu hai chiều độ trễ thấp tăng lên — cộng tác thời gian thực, metaverse, telemetry IoT, phản hồi truyền dòng của AI tạo sinh (truyền theo từng token), và hơn thế. Cũng cần cân nhắc đồng thời sự liên kết của nó với thiết kế message broker (Kafka), service mesh, và tính quan sát được (Observability).
Tài liệu tham khảo
- IETF RFC 6455, "The WebSocket Protocol", https://www.rfc-editor.org/rfc/rfc6455
- IETF RFC 7692, "Compression Extensions for WebSocket", https://www.rfc-editor.org/rfc/rfc7692
- IETF RFC 8441, "Bootstrapping WebSockets with HTTP/2", https://www.rfc-editor.org/rfc/rfc8441
- MDN Web Docs, "The WebSocket API (WebSockets)", https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
Tóm tắt một câu: WebSocket là giao thức nâng cấp một kết nối TCP duy nhất thành kênh song công qua bắt tay HTTP Upgrade để server và client trao đổi truyền thông hai chiều thời gian thực với chi phí phụ trội tối thiểu; nó phù hợp với chat, game, cộng tác và giao dịch nơi tính hai chiều độ trễ thấp là thiết yếu, nhưng phải thiết kế đồng thời khả năng mở rộng của kết nối có trạng thái và bảo mật CSWSH.