Bỏ qua tới nội dung
LẬP TRÌNHSO SÁNH

WebSocket và Socket.IO khác nhau thế nào? Phân biệt chi tiết để không chọn nhầm

WebSocket là giao thức chuẩn, Socket.IO là thư viện xây bên trên. Bài này giải thích cơ chế bắt tay, khung dữ liệu, cơ chế dự phòng, cái bẫy không tương thích và khi nào nên chọn cái nào.

BBeernary30/10/2026 · 11 PHÚT ĐỌC
COVER.BMP
WebSocket và Socket.IO khác nhau thế nào? Phân biệt chi tiết để không chọn nhầm
▲ WebSocket là giao thức, Socket.IO là thư viện chạy trên nó

Bạn vừa dựng xong một server chat bằng Socket.IO, rồi hăm hở viết client bằng new WebSocket("ws://localhost:3000") vì nghĩ "Socket.IO thì cũng là WebSocket thôi mà". Kết quả: client cứ treo ở trạng thái kết nối, hoặc nhận về mấy chuỗi khó hiểu kiểu 0{"sid":"..."} rồi bị ngắt. Lỗi này phổ biến đến mức gần như lập trình viên nào làm realtime cũng từng dính một lần.

Nguyên nhân nằm ở chỗ hai cái tên nghe giống nhau nhưng thuộc hai tầng khác hẳn: WebSocket là một giao thức chuẩn, còn Socket.IO là một thư viện xây bên trên. Bài này sẽ mổ xẻ từng lớp: chúng sinh ra để giải quyết chuyện gì, chạy thế nào, vì sao không nói chuyện được với nhau, và khi nào nên chọn cái nào.

Vì sao HTTP thôi là chưa đủ?

HTTP vận hành theo kiểu hỏi – đáp (request – response): trình duyệt gửi yêu cầu, máy chủ trả lời, xong là hết lượt. Máy chủ không có cách nào tự "gõ cửa" trình duyệt khi có tin nhắn mới, giá cổ phiếu vừa nhảy hay đơn hàng vừa đổi trạng thái.

Trước khi có WebSocket, người ta xoay xở bằng hai mẹo:

  • Polling (hỏi định kỳ): cứ vài giây trình duyệt lại hỏi "có gì mới không?". Phần lớn câu trả lời là "không có gì", nhưng mỗi lần hỏi vẫn tốn nguyên một yêu cầu HTTP kèm hàng trăm byte header, cookie. Hỏi dày thì tốn băng thông và nặng máy chủ, hỏi thưa thì tin đến chậm.
  • Long polling (hỏi rồi chờ): trình duyệt gửi yêu cầu, máy chủ giữ yêu cầu đó lại cho tới khi có dữ liệu mới mới trả lời, sau đó trình duyệt lập tức hỏi tiếp. Đỡ lãng phí hơn, nhưng vẫn là mỗi tin một vòng HTTP, và máy chủ phải giữ rất nhiều yêu cầu treo.

Hãy hình dung polling giống như cứ năm phút lại gọi điện hỏi shipper "tới chưa anh?", trong khi điều bạn thực sự cần là shipper tự gọi khi đến nơi.

Dãy tủ máy chủ trong trung tâm dữ liệu, nơi phải gánh hàng nghìn yêu cầu polling mỗi giây

WebSocket hoạt động thế nào?

WebSocket là giao thức được chuẩn hoá trong tài liệu RFC 6455 năm 2011. Nó mở một kết nối hai chiều, luôn mở (full-duplex) giữa trình duyệt và máy chủ trên một kết nối TCP duy nhất. Ai có dữ liệu thì gửi, không cần chờ bên kia hỏi.

Bắt tay bằng HTTP rồi "lột xác"

Kết nối bắt đầu bằng một yêu cầu HTTP bình thường, chỉ khác là có header Upgrade:

http
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

Nếu đồng ý, máy chủ trả mã 101 Switching Protocols kèm Sec-WebSocket-Accept (được tính từ key của client để chứng minh nó thực sự hiểu WebSocket). Từ giây phút đó, hai bên không nói HTTP nữa mà trao đổi bằng các khung dữ liệu (frame). Địa chỉ dùng tiền tố ws:// (cổng 80) hoặc bản mã hoá wss:// (cổng 443).

Vì sao WebSocket nhẹ đến vậy?

Mỗi frame chỉ có phần tiêu đề từ 2 đến 14 byte: 2 byte cơ bản, thêm vài byte độ dài nếu gói lớn, và 4 byte mã che (masking key) với frame đi từ client lên. So với hàng trăm byte header của mỗi yêu cầu HTTP, đây là lý do WebSocket gần như không tốn gì thêm cho mỗi tin và độ trễ rất thấp.

Màn hình bảng giá chứng khoán cập nhật liên tục, kiểu dữ liệu mà WebSocket được sinh ra để truyền

Phía trình duyệt, API có sẵn và rất gọn:

javascript
const ws = new WebSocket("wss://example.com/chat");
ws.onopen = () => ws.send("xin chào");
ws.onmessage = (e) => console.log("nhận:", e.data);
ws.onclose = () => console.log("mất kết nối");

Cái giá của sự tối giản

WebSocket thuần chỉ lo đúng một việc là chuyển byte qua lại. Mọi thứ còn lại bạn phải tự viết:

  • Không tự nối lại: rớt Wi-Fi, đổi từ 4G sang Wi-Fi, server khởi động lại là kết nối chết hẳn.
  • Không có sự kiện đặt tên: chỉ có một luồng message, muốn phân biệt "tin chat" với "đang gõ phím" thì tự quy ước định dạng.
  • Không có phòng (room): muốn gửi tin cho đúng 20 người trong một nhóm thì tự quản lý danh sách kết nối.
  • Không có xác nhận đã nhận (acknowledgement): gửi đi rồi không biết bên kia đã xử lý chưa.

Mặt sau tủ máy chủ chằng chịt dây mạng, mỗi sợi đứt là một kết nối WebSocket phải tự nối lại

Socket.IO thêm gì lên trên WebSocket?

Socket.IO là một thư viện gồm cả phần client và phần server (bản server chính thức viết cho Node.js, ngoài ra có các bản cộng đồng cho Python, Java, Go...). Nó dùng WebSocket làm đường truyền chính, nhưng bọc thêm một lớp giao thức riêng để lấp những chỗ trống kể trên.

Cơ chế dự phòng: không có đường cao tốc thì đi đường làng

Bên dưới Socket.IO là tầng Engine.IO, phụ trách việc giữ kết nối. Mặc định nó mở kết nối bằng HTTP long polling trước, rồi mới thử nâng cấp lên WebSocket. Nếu proxy công ty, tường lửa hay mạng khách sạn chặn WebSocket, kết nối vẫn chạy được bằng long polling. Người dùng chỉ thấy app chậm hơn chút, không thấy app chết.

Sơ đồ mạng văn phòng với tường lửa, router và switch, nơi WebSocket hay bị chặn

Bộ đồ nghề có sẵn

  • Tự động kết nối lại, có độ trễ tăng dần để không dội bom server.
  • Sự kiện có tên: socket.emit("chat", data) và socket.on("chat", ...).
  • Phòng: socket.join("nhom-1") rồi io.to("nhom-1").emit(...) để gửi cho cả nhóm.
  • Namespace: tách kênh trên cùng một kết nối, ví dụ /admin và /chat.
  • Acknowledgement: truyền thêm một callback để biết chắc bên kia đã nhận.
  • Heartbeat: gói ping/pong định kỳ (mặc định 25 giây một lần) để phát hiện kết nối "chết lâm sàng".
javascript
// server
io.on("connection", (socket) => {
  socket.join("phong-1");
  socket.on("chat", (msg, ack) => {
    io.to("phong-1").emit("chat", msg);
    ack("đã nhận");
  });
});

Cái bẫy lớn nhất: hai bên không cùng "chuẩn phích cắm"

Đây là chỗ nhiều người vấp nhất. Socket.IO không phải là một bản cài đặt WebSocket. Mỗi gói tin của nó có định dạng riêng: mã loại gói của Engine.IO, mã loại gói của Socket.IO, rồi mới tới tên sự kiện và dữ liệu dạng JSON. Một tin chat trên dây trông như thế này:

text
42["chat","xin chào"]

Số 4 là "message" của Engine.IO, số 2 là "event" của Socket.IO. Client WebSocket thuần không biết những con số đó nghĩa là gì, cũng không biết phải trả lời ping của server. Kết quả:

  • Client WebSocket thuần gọi server Socket.IO: không kết nối được, hoặc kết nối xong nhận gói lạ rồi bị ngắt vì không trả lời đúng giao thức.
  • Client Socket.IO gọi server WebSocket thuần: client gửi yêu cầu tới đường dẫn /socket.io/?EIO=4&transport=polling, server không hiểu, trả lỗi, client cứ thế thử lại mãi.

Ổ cắm chuyển đổi du lịch với nhiều kiểu chân cắm khác chuẩn

Giống như phích cắm chuẩn Mỹ và ổ cắm chuẩn châu Âu: cùng là điện, nhưng không cắm vừa nhau. Muốn nói chuyện thì hai đầu phải cùng dùng Socket.IO, hoặc cùng dùng WebSocket thuần.

Dấu hiệu bạn đang dính bẫy này

Tab Network của trình duyệt liên tục có yêu cầu tới /socket.io/... trả về 404, hoặc kết nối WebSocket mở rồi đóng ngay với gói đầu tiên bắt đầu bằng số 0. Kiểm tra lại xem client và server có dùng cùng một thứ không.

Hiệu năng, dung lượng và khi mở rộng nhiều máy chủ

Overhead trên dây

Vì mỗi tin Socket.IO kèm thêm mã gói và tên sự kiện, cộng với nhịp ping/pong định kỳ, nên nó nặng hơn WebSocket thuần một chút. Với ứng dụng chat vài tin mỗi giây, khác biệt này không đáng kể. Với hệ thống đẩy hàng nghìn tin nhỏ mỗi giây, vài byte mỗi tin cộng dồn lại thành con số đáng để ý.

Dung lượng phía client

WebSocket có sẵn trong mọi trình duyệt hiện đại, không phải tải gì thêm. Thư viện client của Socket.IO nặng hơn 10 KB sau khi nén, đổi lại bạn có đủ tính năng ở trên mà không phải tự viết.

Khi chạy nhiều máy chủ

Một máy chủ chỉ biết các kết nối của chính nó. Khi mở rộng ra nhiều máy:

  • Socket.IO cần một adapter, phổ biến nhất là Redis adapter, để một lệnh io.to("phong-1").emit() đi tới được người dùng đang nối vào máy khác.
  • Nếu còn bật long polling, cần sticky session ở load balancer: các yêu cầu polling của cùng một client phải về đúng một máy, nếu không phiên sẽ bị lỗi.
  • WebSocket thuần thì không có sẵn gì: bạn tự dựng lớp pub/sub (Redis, NATS...) để phát tin chéo giữa các máy.
Mẹo bỏ sticky session

Nếu bạn chắc chắn mạng của người dùng không chặn WebSocket, có thể cấu hình client Socket.IO với transports: ["websocket"] để bỏ qua long polling. Khi đó không cần sticky session nữa, nhưng bạn cũng mất luôn cơ chế dự phòng.

Tiêu chí WebSocket thuần Socket.IO
Bản chất Giao thức chuẩn (RFC 6455) Thư viện client + server
Overhead mỗi tin 2–14 byte tiêu đề frame Thêm mã gói, tên sự kiện, ping/pong
Dung lượng client 0 (có sẵn trong trình duyệt) Hơn 10 KB sau nén
Tự nối lại Không Có
Phòng, namespace, ack Tự viết Có sẵn
Dự phòng khi bị chặn Không Lùi về HTTP long polling
Tương thích chéo ngôn ngữ Mọi ngôn ngữ đều có thư viện chuẩn Phải dùng bản Socket.IO tương ứng
Mở rộng nhiều máy Tự dựng pub/sub Adapter (Redis...) + sticky session

Nên chọn WebSocket thuần hay Socket.IO?

Chọn WebSocket thuần khi

  • Cần hiệu năng tối đa, độ trễ thấp nhất: sàn giao dịch, bảng giá realtime, game nhiều người chơi gửi vị trí liên tục.
  • Kết nối với hệ thống viết bằng ngôn ngữ khác hoặc dịch vụ bên thứ ba: hầu hết API realtime công khai nói WebSocket chuẩn, không nói Socket.IO.
  • Thiết bị IoT tài nguyên hạn chế, cần gửi dữ liệu cảm biến đều đặn với chi phí nhỏ nhất.

Người chơi đeo tai nghe chơi game bắn súng trực tuyến trên máy tính

Bo mạch Raspberry Pi gắn bảng cảm biến, kiểu thiết bị IoT gửi dữ liệu liên tục

Chọn Socket.IO khi

  • Làm ứng dụng chat, thông báo, bảng điều khiển cộng tác, nơi phòng, sự kiện và xác nhận dùng hằng ngày.
  • Người dùng có thể ở mạng khó: mạng công ty, khách sạn, proxy chặn WebSocket.
  • Cả client lẫn server đều dùng JavaScript và bạn cần ra sản phẩm nhanh, ổn định, không muốn tự viết lại phần nối lại hay quản lý phòng.

Minh hoạ hai người trò chuyện qua ứng dụng chat trên điện thoại

Câu hỏi tự kiểm

Trước khi chọn, tự hỏi: "Mình có định tự viết lại cơ chế nối lại, phòng và xác nhận không?". Nếu có và bạn cần kiểm soát từng byte, chọn WebSocket thuần. Nếu không, Socket.IO sẽ tiết kiệm cho bạn nhiều tuần công.

WebSocket là con đường, còn Socket.IO là chiếc xe chạy trên con đường đó, kèm sẵn bản đồ và phụ tùng.

Tóm tắt nhanh

  • WebSocket là giao thức chuẩn (RFC 6455, 2011), mở kết nối hai chiều luôn mở, frame chỉ 2–14 byte tiêu đề.
  • Socket.IO là thư viện dùng WebSocket làm đường truyền chính, thêm tự nối lại, sự kiện có tên, phòng, namespace, ack và dự phòng long polling.
  • Hai thứ không tương thích trực tiếp: client WebSocket thuần không nói chuyện được với server Socket.IO và ngược lại.
  • Socket.IO nặng hơn một chút trên dây và phía client, đổi lại đỡ rất nhiều công viết code.
  • Hiệu năng tối đa, đa ngôn ngữ, IoT: WebSocket thuần. Chat, thông báo, cộng tác, mạng khó: Socket.IO.

Câu hỏi thường gặp

Socket.IO có phải là WebSocket không?

Không. Socket.IO dùng WebSocket làm đường truyền khi có thể, nhưng có giao thức riêng bọc bên trên, nên chỉ client Socket.IO mới nói chuyện được với server Socket.IO.

Trình duyệt bây giờ đều hỗ trợ WebSocket, vậy cơ chế dự phòng còn cần không?

Trình duyệt thì hỗ trợ, nhưng mạng ở giữa (proxy công ty, tường lửa, một số mạng công cộng) vẫn có thể chặn. Dự phòng long polling giúp app không chết hẳn trong các trường hợp đó.

Server viết bằng Python hay Go có dùng được Socket.IO không?

Được, có các bản server Socket.IO do cộng đồng phát triển cho nhiều ngôn ngữ. Tuy vậy, nếu hệ thống của bạn cần kết nối với nhiều dịch vụ khác nhau, WebSocket chuẩn vẫn là "ngôn ngữ chung" dễ tích hợp hơn.

WebSocket thuần có cơ chế ping/pong không?

Có, giao thức WebSocket có frame điều khiển ping và pong, nhưng API WebSocket trong trình duyệt không cho JavaScript tự gửi ping. Muốn phát hiện kết nối chết hay tự nối lại ở phía client, bạn vẫn phải tự viết logic.

BVIẾT BỞIBeernary

ĐỌC TIẾP_

BÌNH LUẬN (0)

LOGIN.EXE
> ĐĂNG NHẬP ĐỂ BÌNH LUẬNDùng tài khoản Google, không cần tạo mật khẩu. Tên và ảnh đại diện Google sẽ hiện cạnh bình luận.

> CHƯA CÓ BÌNH LUẬN. HÃY LÀ NGƯỜI ĐẦU TIÊN_