Bài viết này sẽ tìm hiểu các khái niệm trong Socket.IO, nội dung chính của nó sẽ nằm ở mức cơ bản cho người mới bắt đầu. Sẽ có code mẫu cho triển khai socket.io ở cả phía server và client.
Socket.io là gì ?
Socket.io được tạo vào năm 2010. Nó được phát triển để tạo kết nối mở cho giao tiếp với thời gian thực, và là một hiện tượng vào thời điểm đó.
Socket.io cho phép giao tiếp hai chiều giữa client và server. Giao tiếp hai chiều được kích hoạt khi client có socket.io trên trình duyệt và server cũng có tích hợp với socket.io. Dữ liệu được gửi có thể ở nhiều định dạng, đơn giản nhất là dùng JSON.
Để thiết lập kết nối, và thay đổi dữ liệu giữa client và server, socket.io sử dụng engine.io. Đây là một triển khai thấp hơn ở bên trong nó. Engine.io được dữ dụng cho triển khai phía server và engine.io-client được dùng cho client.
Socket.io gợi nhớ đến WebSockets. Websocket cũng là một triển khai trình duyệt cho phép giao tiếp hai chiều, tuy nhiên socket.io không sử dụng tiêu chuẩn này. Đầu tiên socket.io tạo ra một kết nối long-polling (kết nối dài hạn) sử dụng xhr-polling. Sau đó, khi điều này được thiết lập, nó sẽ nâng cấp lên phương thức kết nối tốt nhất hiện có. Trong hầu hết trường hợp, nó sẽ dẫn đến một kết nối WebSocket. Xem cách WebSocket chống lại long-polling (và tại sao WebSocket luôn là lựa chọn tốt nhất).
Socket.io - Thực hiện
Một cách phổ biến để minh hoạ giao tiếp hai chiều mà socket.io cung cấp là một ứng dụng chat. Với socket, khi server nhận về một tin nhắn mới, nó sẽ gửi cho client và thông báo với họ, bỏ qua các yêu cầu cần gửi giữa client và server. Một ứng dụng chat sẽ biểu diễn cách socket.io hoạt động.
Ví dụ
Server
Trước tiên bạn cần cài đặt node.js và sử dụng express để xây dựng.
Tạo thư mục:
$ mkdir socket.io-example
cd socket.io-example
npm install socket.io express
Các package mà ta sẽ sử dụng:
const app = require("express")();
const http = require("http").createServer(app);
const io = require("socket.io")(http);
Ở đây server sẽ gửi một file html để thiết lập nhanh:
app.get("/", (req, res) => res.sendFile(__dirname + "/index.html"));
Bây giờ ta thiết lập socket.io, ta sẽ lắng nghe sự kiện “connection” và chạy các hàm trong nó bất cứ khi nào nó diễn ra:
io.on("connection", function(socket) {
console.log("socket connected”);
});
Trong hàm này, sử dụng io.emit()
để gửi tin nhắn mới đến tất cả client được kết nối.
io.on("connection", function(socket) {
io.emit("user connected”);
});
Nếu bạn muốn thông báo cho tất cả ngoại trừ người đã kết nối, bạn có thể dùng socket.broadcast.emit()
.
Chúng ta cũng sẽ thêm listener cho bất kỳ tin nhắn mới nào nhận được từ client và gửi tin nhắn tới tất cả người dùng để phản hồi.
io.on("connection", function(socket) {
io.emit("user connected”);
socket.on("message", function(msg) {
io.emit("message", msg);
});
});
Chạy ứng dụng trên cổng 3000:
http.listen(3000, () => console.log("listening on http://localhost:3000")
Client
Trở lại với file index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Socket.io Example</title>
</head>
<body>
<h1>Our Socket.io Chat Application</h1>
<div>
<h2>Messages</h2>
<ul></ul>
</div>
<form action="">
<input type="text" />
<button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
</body>
</html>
Sử dụng thẻ script
trong html để thiết lập kết nối.
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
</script>
Bây giờ ta thêm code logic vào:
<script>
// select relevant elements
const form = document.querySelector("form");
const input = document.querySelector("input");
messageList = document.querySelector("ul");
// establish socket.io connection
const socket = io();
// handle sending message to server & input reset
function sendMessage(e) {
// prevent form submission refreshing page
e.preventDefault();
// send input value to server as type 'message'
socket.emit("message", input.value);
// reset input value
input.value = "";
}
// add listener to form submission
form.addEventListener("submit", sendMessage);
// add message to our page
function addMessageToHTML(message) {
// create a new li element
const li = document.createElement("li");
// add message to the elements text
li.innerText = message;
// add to list of messages
messageList.append(li);
}
// watch for socket to emit a 'message'
socket.on("message", addMessageToHTML);
// display message when a user connects
function alertUserConnected() {
addMessageToHTML("User connected");
}
// watch for socket to emit a 'user connected' event
socket.on("user connected", alertUserConnected);
</script>
Điểm chính ở đây là các hàm socket.on(event, callback)
. Khi server của phát ra các sự kiện khớp với đối số event đầu tiên, lệnh callback sẽ được chạy. Bên trong các lệnh callback này, ta có thể thực hiện các hành động mà chúng ta muốn ở phía client. Trong trường hợp này, nó hiển thị thông báo trên màn hình.
Duy trì & Vận hành Socket.IO
Như đã giải thích ở trên, việc bắt đầu với socket.io tương đối đơn giản - tất cả những gì bạn cần là một server Node.js để chạy nó. Nếu bạn muốn bắt đầu với một ứng dụng realtime cho một số lượng người dùng hạn chế, socket.io là một lựa chọn tốt. Tuy nhiên, các vấn đề sẽ xảy ra khi làm việc ở quy mô lớn. Ví dụ: giả sử bạn muốn xây dựng một ứng dụng giống như CRM cho phép giao tiếp giữa các doanh nghiệp. Socket.io được xây dựng trên các thư viện network không đồng bộ và sẽ gây ra tải trên server của bạn. Việc duy trì kết nối với người dùng cũng như gửi và nhận tin nhắn sẽ gây ra quá tải và nếu client bắt đầu gửi một lượng dữ liệu đáng kể qua socket.io, nó phải truyền dữ liệu theo từng phần, giải phóng tài nguyên khi truyền dữ liệu. Vì vậy, khi ứng dụng của bạn thu hút nhiều người dùng hơn và server của bạn đạt mức tải tối đa, bạn sẽ cần phải chia kết nối qua nhiều server hoặc có nguy cơ mất thông tin quan trọng.
Thật không may, điều này không đơn giản như thêm một server khác. Sockets là một kết nối mở giữa server và client. Server chỉ biết về những client đã kết nối trực tiếp với nó chứ không phải những client đã kết nối với các server khác. Quay trở lại chức năng trò chuyện, hãy tưởng tượng bạn muốn phát một thông báo đến tất cả người dùng rằng ai đó đã tham gia trò chuyện. Nếu họ được kết nối với một server khác, họ sẽ không nhận được thông báo này.
Để giải quyết vấn đề này, bạn cần có một pub/sub (ví dụ: Redis). Thứ này này sẽ giải quyết vấn đề nói trên bằng cách thông báo cho tất cả các server rằng họ cần gửi tin nhắn khi ai đó tham gia trò chuyện. Thật không may, điều này có nghĩa là phải thêm một cơ sở dữ liệu bổ sung để duy trì mà rất có thể sẽ cần thêm server riêng cho nó.
Socket.io đã tạo một bộ điều hợp socket.io-adapter hoạt động với pub/sub và các server để chia sẻ thông tin. Bạn có thể viết cách triển khai bộ điều hợp này của riêng mình hoặc bạn có thể sử dụng bộ điều hợp mà người ta đã cung cấp cho Redis, may mắn thay, socket.io rất dễ tích hợp.
Các công cụ nâng cao độ tin cậy khác cho socket.io có thể bao gồm CoreOS để chia nhỏ kiến trúc thành các đơn vị có thể được phân phối trên phần cứng có sẵn, giới thiệu các phiên bản mới khi tải tăng lên.
Một vấn đề khác với việc mở rộng quy mô socket.io là trong khi WebSockets giữ kết nối của họ mở, nếu kết nối trở lại trạng thái polling thì sẽ có nhiều yêu cầu trong suốt thời gian kết nối. Khi một trong những yêu cầu này chuyển đến một server khác, bạn sẽ nhận được lỗi Error during WebSocket handshake: Unexpected response code: 400
.
Hai cách chính để giải quyết vấn đề này là định tuyến client dựa trên địa chỉ gốc của họ hoặc cookie. Socket.io có tài liệu tuyệt vời về cách giải quyết vấn đề này cho các môi trường khác nhau.
Mặc dù socket.io thường có nhiều tài liệu tốt để khắc phục những hạn chế của nó, nhưng chúng thường được coi là remedies(biện pháp khắc phục) thay vì giải pháp. Nếu bạn có ý định mở rộng quy mô hơn nữa, những cách được đề xuất này cuối cùng sẽ làm tăng thêm độ phức tạp và thêm lợi nhuận do lỗi cho ngăn xếp của bạn.
Khi nào socket.io đạt đến giới hạn của nó ?
Socket.IO thực sự làm cho nhiều thứ dễ dàng hơn so với việc tự thiết lập socket, nhưng cũng có những hạn chế và nhược điểm ngoài vấn đề mở rộng được đề cập ở trên.
Đầu tiên là kết nối ban đầu dài hơn so với WebSockets. Điều này là do trước tiên nó thiết lập kết nối bằng cách sử dụng long-polling và xhr-polling, sau đó nâng cấp lên WebSockets nếu có.
Nếu bạn không cần hỗ trợ các trình duyệt cũ hơn và không lo lắng về môi trường ứng dụng khách không hỗ trợ WebSockets, bạn có thể giảm thiểu tác động này bằng cách chỉ định chỉ kết nối với WebSockets. Thao tác này sẽ thay đổi kết nối ban đầu tới WebSocket, nhưng loại bỏ bất kỳ fallback nào.
Thay đổi ở server:
io.set("transports", ["websocket"]);
Thay đổi ở client:
const socket = io({transports: ["websocket”], upgrade: false});
Trong trường hợp này, client vẫn cần tải xuống tệp js socket.io 61,2 KB. Thông tin thêm về quá trình này là ở đây.
Đối với phát trực tuyến dữ liệu, chẳng hạn như phát trực tuyến video, socket không phải là câu trả lời. Nếu bạn muốn hỗ trợ trao đổi dữ liệu ở cấp độ này, giải pháp tốt hơn là WebRTC.
Socket.io trong tương lai
Vào ngày 9 tháng 3 năm 2021, socket.io bản v4 đã được phát hành. Nó là một API một số tính năng mới như hỗ trợ được chờ đợi nhiều cho sự kiện đã nhập, tính bất biến và một số bản sửa lỗi.
Nhìn vào lượt tải xuống NPM, việc sử dụng socket.io ngày càng tăng.
Tuy nhiên, Sockjs và WS đang phát triển đều đặn và vượt xa socket.io về lượt tải xuống NPM.
Điều này cho thấy rằng mặc dù việc sử dụng các socket đã tăng lên, các dev vẫn chọn các giải pháp thay thế cho Socket.IO. Một số đã chọn các gói như WS hoặc SockJS. Những người khác đã lựa chọn các giải pháp hosted trong đó sự phức tạp của tin nhắn realtime được xử lý cho bạn và nhiều người trong số họ vận hành các mô hình freemium.
Như bạn có thể thấy trong hình bên dưới, tất cả các trình duyệt hiện đại hiện nay đều hỗ trợ WebSockets. Điều này phủ nhận một số nhu cầu về gói xử lý các kết nối socket trên trình duyệt và giải thích sự gia tăng phổ biến của các gói như WS xử lý kết nối socket phía server, nhưng dựa vào API trình duyệt gốc cho các kết nối và giao tiếp phía client .
Như chúng ta đã tìm hiểu, socket.io là một công cụ tuyệt vời cho các dev muốn thiết lập kết nối socket hai chiều giữa client và server. Điều này làm cho các ứng dụng nhỏ như trò chuyện trực tiếp trở nên đơn giản hơn nhiều. Socket.io làm cho nhiều thứ dễ dàng hơn và cung cấp dự phòng cho các client không được hỗ trợ, nhưng có sự đánh đổi của riêng nó.
Chia tỷ lệ ứng dụng có lẽ là bước khó nhất trong việc sử dụng socket và việc triển khai socket.io cho các kết nối không phải WebSocket càng làm phức tạp thêm quy trình. Hỗ trợ trong tương lai của socket.io cũng đáng ngờ.
Ngoài câu hỏi về hỗ trợ trong tương lai, việc có sử dụng socket.io hay không, thực sự phụ thuộc vào trường hợp sử dụng cá nhân - để bắt đầu xây dựng các ứng dụng realtime đơn giản, socket.io hoạt động tốt. Với sự hỗ trợ của WebSocket được phổ biến rộng rãi (đáp ứng nhu cầu về các ứng dụng và dịch vụ realtime tăng trưởng rất lớn kể từ khi socket.io được thành lập vào năm 2010), giờ đây có nhiều lựa chọn hơn để sử dụng các gói tương tự gần với triển khai gốc hơn, vì vậy đáng để so sánh socket.io với cả những thứ này. Đối với các ứng dụng phức tạp hơn hoặc ứng dụng mà bạn nghĩ sẽ mở rộng quy mô, hãy chuẩn bị thêm công nghệ khác vào ngăn xếp của bạn.