Hiện nay, WebSocket được biết đến rộng rãi như một giao thức phổ biến trong các ứng dụng thời gian thực, nhằm trao đổi thông tin trực tuyến hai chiều và liên tục giữa client và server, client và client.
1. Giới thiệu WebSocket
1.1 Định nghĩa WebSocket
Giao thức WebSocket
là một tiêu chuẩn mở được hỗ trợ rộng rãi để phát triển các ứng dụng thời gian thực. Các phương pháp trước đây để mô phỏng kết nối song công hoàn toàn dựa trên Polling, một phương pháp đồng bộ trong đó client đưa ra yêu cầu tới server để xem liệu có bất kỳ thông tin nào có sẵn hay không.
Tính năng Polling hoạt động tốt trong trường hợp biết chính xác khoảng thời gian có sẵn của tin nhắn. Tuy nhiên, trong hầu hết các ứng dụng thời gian thực, tần suất tin nhắn thường không thể đoán trước được. Ngoài ra, polling yêu cầu client mở và đóng nhiều kết nối không cần thiết.
Long Polling (còn được gọi là Comet) là một phương thức liên lạc phổ biến khác trong đó client mở kết nối với server trong một khoảng thời gian nhất định. Nếu server không có bất kỳ thông tin nào, nó sẽ giữ yêu cầu mở cho đến khi có thông tin hoặc hết thời hạn được chỉ định (hết thời gian chờ).
Về cơ bản, Comet trì hoãn việc hoàn thành phản hồi HTTP cho đến khi server có thứ gì đó cần gửi cho client, một kỹ thuật thường được gọi là hanging-GET hoặc pending-POST.
Việc client phải liên tục kết nối lại với server để có thông tin mới khiến Long Polling trở thành một lựa chọn khá tệ đối với nhiều ứng dụng thời gian thực.
WebSocket
là một giao thức truyền thông máy tính (computer communication protocol), cung cấp các kênh liên lạc dạng full-duplex (song công) qua một kết nối TCP. Giao thức WebSocket
hiện nay được bao hàm ngay ở mục Connectivity trong đặc tả của HTML5.
WebSocket
is a computer communications protocol, providing full-duplex communication channels over a single TCP connection.
Nguồn: WebSocket - Wikipedia
Sử dụng WebSocket
, bạn có thể tạo ra những ứng dụng realtime thật sự như chat, chỉnh sửa tài liệu online (ví dụ Google docs), giao dịch hoặc game online nhiều người chơi...
1.2 Lý do WebSocket xuất hiện
1.2.1 Duplex
Hệ thống liên lạc song công (duplex) là một hệ thống điểm-điểm bao gồm hai hoặc nhiều bên được kết nối có thể giao tiếp với nhau theo cả hai hướng. Hệ thống song công được sử dụng trong nhiều mạng truyền thông, giúp liên lạc đồng thời theo cả hai hướng giữa hai bên được kết nối.
Có hai loại hệ thống truyền thông song công: song công hoàn toàn full-duplex (FDX) và bán song công half-duplex (HDX).
A duplex communication system is a point-to-point system composed of two or more connected parties or devices that can communicate with one another in both directions. Duplex systems are employed in many communications networks, either to allow for simultaneous communication in both directions between two connected parties or to provide a reverse path for the monitoring and remote adjustment of equipment in the field. There are two types of duplex communication systems: full-duplex (FDX) and half-duplex (HDX).
Cre: wikipedia
1.2.2 Half duplex
Với giao thức truyền thống HTTP/1.0 và HTTP/1.1 trong mô hình client - server, client gửi một yêu cầu HTTP đến server, server xử lý và gửi kết quả trả về cho client, bao gồm trang HTML cũng như các thông tin liên quan. HTTP/1.0 đã đủ để thực hiện một yêu cầu lấy tài liệu từ một server.
Với HTTP/1.1 thêm vào các kết nối tái sử dụng, trình duyệt có thể khởi tạo một kết nối đến một server web để lấy các trang HTML, sau đó sử dụng cùng một kết nối để lấy nguồn tài nguyên như hình ảnh, chữ viết, và như vậy. HTTP / 1.1 giảm độ trễ giữa yêu cầu bằng cách giảm số lượng các kết nối đã được thực hiện từ các client đến các server.
Về bản chất, HTTP cũng là half-duplex, có nghĩa là lưu lượng truyền tin theo một hướng duy nhất tại một thời điểm, điều này gây lãng phí và kém hiệu quả.
A half-duplex (HDX) system provides communication in both directions, but only one direction at a time, not simultaneously in both directions.
Nguồn: WebSocket - Wikipedia
1.2.3 Full duplex
Hiện nay ứng dụng web đã phát triển khác xa so với ngày đầu nó xuất hiện, kèm theo đó là vô số các kỹ thuật mới được áp dụng để phục vụ cho quá trình này nhằm đem lại trải nghiệm mới mẻ, đầy hứng thú và cũng không kém phần tiện dụng cho người dùng.
Công nghệ web thời gian thực (realtime) ngày càng trở nên phổ biết. Có nhiều công nghệ, phương pháp giúp xây dựng ứng dụng thời gian thực có thể kể đến như
- AJAX LONG-POLLING
- SERVER SENT EVENTS (SSE)
- COMET
- WEBSOCKET
Và các công nghệ yêu cầu "thời gian thực" hoặc gần như "thời gian thực" hiện nay thì half-duplex không còn thể đáp ứng. Cho đến hiện tại, WEBSOCKET
dạng full-duplex đươc tích hợp trong HTML 5 đang trở lên chiếm ưu thế tuyệt đối.
A full-duplex (FDX) system allows communication in both directions, and, unlike half-duplex, allows this to happen simultaneously.
Nguồn: WebSocket - Wikipedia
2. Những điều cần biết về WebSocket
2.1 Tạo một kết nối WebSocket
Để kết nối với server từ xa, hãy tạo một phiên bản đối tượng WebSocket
mới và cung cấp cho đối tượng mới URL của điểm cuối đích (endpoint).
Kết nối websocket được thiết lập bằng cách nâng cấp từ giao thức HTTP lên Giao thức websocket trong quá trình bắt tay ban đầu giữa client và server, qua cùng một kết nối TCP cơ bản.
Tiêu đề nâng cấp Upgrade
được bao gồm trong yêu cầu này để thông báo cho server rằng client muốn thiết lập kết nối websocket. Sau khi thiết lập kết nối thành công, các tin nhắn websocket sẽ được gửi và nhận bằng phương pháp được định nghĩa bởi giao diện websocket .
Để tạo kết nối, hãy gọi hàm tạo websocket của Javascript, hàm này trả về đối tượng phiên bản kết nối. Sau đó bạn có thể lắng nghe các sự kiện trên đối tượng đó. Những sự kiện này được kích hoạt khi kết nối mở hoặc đóng, có tin nhắn đến hoặc xảy ra lỗi.
Hàm tạo websocket lấy một đối số bắt buộc URL. Ngoài ra có thêm một số đối số tùy chọn chỉ định một giao thức:
- url - bắt buộc: đường dẫn URL mà bạn muốn thiết lập kết nối
- protocols - tuỳ chọn: một string hoặc mảng string protocol. Những string này để chỉ định sub-protocol, từ đó một server có thể áp dụng nhiều sub-protocol
WebSocket
(ví dụ một server có thể xử lý những loại tương tác khác nhau tuỳ thuộc vào protocol được chỉ định)
// Connecting to the server with a protocol called myProtocol
var ws = new WebSocket(url)
var ws = new WebSocket(url, protocols)
2.2 Handshake
Khi tạo kết nối websocket, bước đầu tiên client và server cần làm là bắt tay (handshake) qua TCP, qua đó client và server đồng ý sử dụng giao thức websocket.
Handshake từ phía client có dạng như sau:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Handshake từ phía server có dạng như sau:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Để xác nhận việc kết nối, client sẽ gửi một giá trị Sec-WebSocket-Key
được mã hóa bằng Based64
đến server.
Sau đó bên server sẽ thực hiện:
- Nối thêm một chuỗi cố định quy định bởi giao thức
WebSocket
là258EAFA5-E914-47DA-95CA-C5AB0DC85B11
vào Sec-WebSocket-Key để được chuỗi mới làdGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
. - Thực hiện mã hóa
SHA-1
chuỗi trên để được1d29ab734b0c9585240069a6e4e3e91b61da1969
. - Mã hóa kết quả vừa nhận được bằng
Base64
để đượcs3pPLMBiTxaQ9kYGzzhZRbK+xOo=
. - Gửi response lại client kèm với giá trị
Sec-WebSocket-Accept
chính là chuỗi kết quả vừa tạo ra.
Client sẽ kiểm tra status code (phải bằng 101) và Sec-WebSocket-Accept
xem có đúng với kết quả mong đợi không và thực hiện kết nối.
Sau khi handshake được thiết lập, API WebSocket
cho phép ứng dụng của bạn kiểm soát giao thức WebSocket
và phản hồi các sự kiện do server kích hoạt. Vì API hoàn toàn được điều khiển theo sự kiện nên khi kết nối song công hoàn toàn được thiết lập.
Khi server có dữ liệu cần gửi cho client hoặc nếu tài nguyên mà ứng dụng đang giám sát thay đổi trạng thái, nó sẽ tự động gửi dữ liệu hoặc thông báo. Với API hướng sự kiện, không cần phải thăm dò server để biết trạng thái cập nhật nhất của tài nguyên được nhắm mục tiêu.
2.3 Handling Binary Data
JavaScript sử dụng text formats giống như JSON and XML để chuyển dữ liệu sang kiểu chuỗi. Tuy nhiên, với HTML5 nó cho phép ứng dụng làm việc với dữ liệu kiểu nhị phân để tăng performance. WebSockets hỗ trợ hai loại dữ liệu nhị phân là large objects (blobs) và ArrayBuffers. Tuy nhiên trong cùng một thời điểm, Websocket chỉ làm việc được với một trong hai kiểu dữ liệu nhị phân trên.
Khi một Websocket được cài đặt, nó sẽ mang dữ liệu mặc định là blobs. Thuộc tính kiểu nhị phân được dùng để chuyển đổi giữa blobs và ArrayBuffer
2.4 WebSocket Events
Bản chất không đồng bộ của WebSocket
có nghĩa là miễn là kết nối WebSocket
được mở, ứng dụng có thể lắng nghe các sự kiện.
Để bắt đầu lắng nghe các sự kiện, hãy thêm các hàm gọi lại vào đối tượng WebSocket
hoặc sử dụng phương thức DOM addEventListener()
để thêm trình xử lý sự kiện vào đối tượng WebSocket.
Một đối tượng WebSocket
có thể gửi 4 sự kiện sau đây:
- Open: Server phản hối yêu cầu kết nối
WebSocket
. Nó cho biết handshake đã được thực hiện và kết nối đã thiết lập. Callback cho sự kiện này làonopen
. - Message: Xảy ra khi nhận data thông qua
WebSocket
. Callback để gọi sự kiện này làonmessage
. - Error: Xảy ra khi kết nối với
WebSocket
đóng bởi vì một lỗi nào đó (ví dụ data không thể gửi). Callback tương ứng làonerror
.
// Event handler for the WebSocket connection opening
ws.onopen = function(e) {
console.log("Connection established");
};
// addEventListener() DOM
ws.addEventListener("open", (e) => {
console.log("Connection established");
});
// Event handler for receiving text messages
ws.onmessage = function(e) {
console.log("Message received", e, e.data);
};
// addEventListener() DOM
ws.addEventListener("message", (e) => {
// Event handler for errors in the WebSocket object
ws.onerror = function(e) {
console.log("WebSocket Error: " , e);
//Custom function for handling errors
handleErrors(e);
};
// addEventListener() DOM
ws.addEventListener("error", (e) => {
console.log("WebSocket error: ", e);
//Custom function for handling errors
handleErrors(e);
});
Chú ý: khi xảy ra error cũng có thể đóng kết nối WebSocket
.
- Close: Xảy ra khi kết nối
WebSocket
đóng. Callback tương ứng làonclose
.
// Event handler for closed connections
ws.onclose = function(e) {
console.log("Connection closed", e);
};
// addEventListener() DOM
ws.addEventListener("close", (e) => {
console.log("Connection closed", e);
});
2.5 WebSocket Methods
WebSocket
cung cấp hai phương thức là:
- send(): phương thức socket.send(data) vận chuyển data thông qua kết nối WebSocket. Nếu vì lý do nào đó mà kết nối không khả thi hoặc kết nối đóng, thì sẽ trả về exception về trạng thái kết nối không hợp lệ.
- close(): công cụ socket.close() được dùng để ngắt kết nối đang tồn tại. Hoặc khi kết nối đã đóng rồi thì sẽ không làm gì cả. Sử dụng với 2 tham số: code (mã trạng thái), reason (chuỗi văn bản giải thích).
// Send a text message
ws.send("This is a message using WebSockets.");
// Close the WebSocket connection
ws.close(1000, "Closing Connection Normally");
2.6 Thuộc tính của đối tượng WebSocket
Một đối tượng kết nối WebSocket
cũng có những thuộc tính sau:
- url (read-only): Trả về URL được thiết lập khi khởi tạo.
- readyState (read-only): Thể hiện trạng thái của kết nối, có thể có các giá trị sau đây:
- 0: kết nối đang tiến triển và vẫn chưa được thiếp lập.
- 1: kết nối được thiết và có thể gửi tin nhắn giữa client và server.
- 2: kết nối đang được xử lý closing handshake.
- 3: kết nối đang đóng hoặc không thể mở.
- bufferedAmount (read-only): Thể hiện số byte của văn bản UTF-8 đang ở hàng đợi của phương thức send(). Ví dụ dưới đây áp dụng thuộc tính này để đảm bảo tin nhắn được gửi khi buffer chưa đầy:
- protocol (read-only): Trả về sub-protocol được chọn bởi server, xuất hiện từ những protocol được liệt kê trong tham số protocols khi khởi tạo đối tượng
WebSocket
. Trả về chuỗi rỗng nếu kết nối không được thiết lập. - binaryType: Kiểm soát kiểu dữ liệu nhị phân nhận được từ kết nối
WebSocket
. Có 2 giá trị là blob (giá trị mặc định) và arraybuffer.
// 6400 max buffer size.
var THRESHOLD = 6400;
// Create a New WebSocket connection
var ws = new WebSocket("ws://echo.websocket.org");
// Listen for the opening event
ws.onopen = function () {
// Attempt to send update every second.
setInterval( function() {
// Send only if the buffer is not full
if (ws.bufferedAmount < THRESHOLD) {
ws.send(getApplicationState());
}
}, 1000);
};
// Connecting to the server with multiple protocol choices
var ws = new WebSocket("ws://echo.websocket.org", [ "protocol", "another protocol"])
ws.onopen = function(e) {
// Check the protocol chosen by the server
console.log( ws.protocol);
}
3. Một số lưu ý khi thiết lập WebSocket
3.1 Supported browsers
Google Chrome là trình duyệt đầu tiên bao gồm hỗ trợ tiêu chuẩn cho WebSockets vào năm 2009. RFC 6455 — Giao thức WebSocket — được chính thức xuất bản trực tuyến vào năm 2011. Giao thức WebSocket và API WebSocket được chuẩn hóa bởi W3C và IETF, đồng thời hỗ trợ trên các trình duyệt rất phổ thông.
Tuy nhiên ngoài việc đa số các trình duyệt tích hợp HTML5 đều hỗ trợ WebSocket, cũng có một vài phiên bản hoặc trình duyệt không hoàn toàn hỗ trợ giao thức WebSocket. Dưới đây là danh sách phiên bản trình duyệt hỗ trợ WebSocket mà lập trình viên cần biết khi tích hợp WebSocket.
Vì thế muốn biết browser hiện tại có hỗ trợ hay không thì có thể dùng đoạn check dưới đây.
if ('WebSocket' in window){ /* WebSocket is supported. You can proceed with your code*/ }
else { /*WebSockets are not supported. Try a fallback method like long-polling etc*/}
3.2 Health-check
Với server websocket, client cần thường xuyên kiểm tra tình trạng kết nối đến server websocket cách định kỳ. Phòng trường hợp gián đoạn ảnh hưởng đến trải nghiệm người dùng của ứng dụng hoặc web.
Quá trình health-check server websocket từ phía client cần quan tâm hai vấn đề: độ trễ và tình trạng kết nối. Trong trường hợp Socket bị ngắ kết nối cần tiến hành xử lý logic tái gửi hoặc nhận lại các thông tin bị trì hoãn.
const WebSocketURL = "wss://example.com/socket"; // Replace with your WebSocket URL
const pingInterval = 5000; // Ping the server every 5 seconds
const maxUnresponsiveTime = 15000; // Consider the connection unhealthy if no response for 15 seconds
let webSocket;
let pingIntervalId;
let lastPongReceived = Date.now();
function establishWebSocketConnection() {
webSocket = new WebSocket(WebSocketURL);
webSocket.onopen = function (event) {
console.log("WebSocket connection opened:", event);
// Start sending pings at regular intervals
pingIntervalId = setInterval(sendPing, pingInterval);
};
webSocket.onclose = function (event) {
console.log("WebSocket connection closed:", event);
// Clear the ping interval and attempt to reconnect
clearInterval(pingIntervalId);
setTimeout(establishWebSocketConnection, 2000); // Retry after 2 seconds
};
webSocket.onerror = function (event) {
console.error("WebSocket error:", event);
};
webSocket.onmessage = function (event) {
console.log("WebSocket message received:", event);
// Handle incoming messages here
// Reset the lastPongReceived timestamp upon receiving any message from the server
lastPongReceived = Date.now();
};
}
function sendPing() {
// Send a ping message to the server
webSocket.send(JSON.stringify({ type: "ping" }));
// Check if the last pong response was received within the acceptable timeframe
const currentTime = Date.now();
if (currentTime - lastPongReceived > maxUnresponsiveTime) {
console.error("WebSocket connection is unhealthy. No pong received.");
webSocket.close(); // Close the connection
}
}
// Initialize the WebSocket connection
establishWebSocketConnection();
3.3 Auto-reconnect
Trong trường hợp, WebSocket server đột ngột mất kết nối client cần tiến hành xử lý logic ngay lập tức phát hiện và thực hiện hành vi tái kết nối. Đảm bảo quá trình hoạt động bình thường của ứng dụng có thể diễn ra xuyên suốt và an toàn.
3.4 Group/broadcast message
Một số ứng dụng trò chuyện yêu cầu quá trình gom nhóm hoặc phân quyền dữ liệu truyền tải trên WebSocket, client cần thực thi thêm một số logic liên quan và đảm bảo tính đúng đắn về logic khi áp dụng WebSocket.
3.5 Performance Background
Một vấn đề quan trọng khi thiết lập WebSocket sẽ luôn giữ kết nối TCP
xuyên suốt quá trình sử dụng, điều này mang lại một số lợi ích và lưu ý mà lập trình viên cần biết.
Lấy ví dụ một developer tạo ra một ứng dụng game chẳng hạn. Ước tính ứng dụng này được 5.000
người chơi đăng nhập cùng lúc. Đồng thời, mỗi giây họ đều có thể gửi/nhận dữ liệu từ server. Làm một phép toán đơn giản để so sánh lượng dữ liệu header truyền tải giữa giao thức HTTP và Websocket trong mỗi giây như sau:
HTTP: 871 x 5,000 = 4,355,000 bytes = 34,790,000 bits per second (33 Mbps)
WebSocket: 2 x 5,000 = 10,000 bytes = 80,000 bits per second (0.0765 Kbps)
Sau khi đọc xong ví dụ đơn giản này cũng có thể thấy được ưu điểm vượt bậc của Websocket so với HTTP về dữ liệu truyền tải Header. Ưu điểm này giúp hiểu rõ hơn về websocket là gì và lý do vì sao nó ngày càng được sử dụng rộng rãi bởi các lập trình viên chuyên nghiệp.
Tuy nhiên, với các ứng dụng di động việc chạy ngầm WebSocket trong background
tiềm ẩn một số rủi ro về bảo mật và tiết kiệm pin. Khi đó, lập trình viên cần nên cân nhắc và có chiến lược tiếp cận phù hợp.
4. Tổng kết
Chúng ta đã tìm hiểu về cách WebSocket
cung cấp một cách tiếp cận hai chiều và liên tục giữa máy khách và máy chủ, là một thay thế hữu ích cho long-polling trong HTML5. WebSocket
mở ra nhiều khả năng mới cho việc phát triển những ứng dụng web hiện đại hai chiều, nhiều người dùng trong thời gian thực.
Đồng thời, chúng ta cũng đã đi sâu tìm hiểu về những gì thực sự diễn ra trước, trong và sau kết nối WebSocket
. Với các kiến thức cần biết về giao thức WebSocket như quy trình bắt tay, các phương thức, thuộc tính và sự kiện xoay quanh đối tượng WebSocket
.
Cuối cùng, mình cũng đã có một số lưu ý cho các lập trình viên khi sử dụng kết nối đến WebSocket
từ phía client. Hi vọng thông qua bài viết, chúng ta sẽ có cách tiếp cận phù hợp sau khi đã nắm những kiến thức cơ bản về giao thức cực kỳ phổ biến và mạnh mẽ này.
5. Tài liệu tham khảo
- https://200lab.io/blog/http2-la-gi/
- https://www.linode.com/docs/guides/introduction-to-websockets/
- https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
Bài viết tiếp theo các bạn nên đọc:
Bài viết liên quan
Hướng dẫn tích hợp Redux và React Query trong dự án React Vite
Nov 22, 2024 • 8 min read
Giới thiệu Kiến trúc Backend for Frontend (BFF)
Nov 16, 2024 • 10 min read
Flask là gì? Hướng dẫn tạo Ứng dụng Web với Flask
Nov 15, 2024 • 7 min read
Webhook là gì? So sánh Webhook và API
Nov 15, 2024 • 8 min read
Spring Boot là gì? Hướng dẫn Khởi tạo Project Spring Boot với Docker
Nov 14, 2024 • 6 min read
Two-Factor Authentication (2FA) là gì? Vì sao chỉ Mật khẩu thôi là chưa đủ?
Nov 13, 2024 • 7 min read