Các ứng dụng web tiêu chuẩn thường bị hạn chế ở các giao thức giao tiếp cụ thể như HTTP và API như WebSocket và WebRTC. Mặc dù rất mạnh mẽ, nhưng các tính năng này được thiết kế để bị hạn chế chặt chẽ nhằm ngăn chặn hành vi sai trái. Chúng không thể thiết lập các kết nối TCP hoặc UDP thô, điều này hạn chế khả năng giao tiếp của các ứng dụng web với các hệ thống cũ hoặc thiết bị phần cứng sử dụng giao thức không phải là giao thức web của riêng chúng. Ví dụ: bạn có thể muốn tạo một ứng dụng SSH dựa trên web, kết nối với máy in cục bộ hoặc quản lý một nhóm thiết bị IoT. Trước đây, việc này đòi hỏi phải có các trình bổ trợ trình duyệt hoặc ứng dụng trợ lý gốc.
Direct Sockets API giải quyết hạn chế này bằng cách cho phép Ứng dụng web tách biệt (IWA) thiết lập các kết nối TCP và UDP trực tiếp mà không cần máy chủ chuyển tiếp. Với IWA, nhờ các biện pháp bảo mật bổ sung (chẳng hạn như Chính sách bảo mật nội dung (CSP) nghiêm ngặt và tính năng cách ly nhiều nguồn), API này có thể được hiển thị một cách an toàn.
Trường hợp sử dụng
Khi nào bạn nên sử dụng Direct Sockets thay vì WebSocket tiêu chuẩn?
- IoT và thiết bị thông minh: Giao tiếp với phần cứng sử dụng TCP/UDP thô thay vì HTTP.
- Hệ thống cũ: Kết nối với các máy chủ thư cũ (SMTP/IMAP), máy chủ trò chuyện IRC hoặc máy in.
- Máy tính từ xa và thiết bị đầu cuối: Triển khai ứng dụng SSH, Telnet hoặc RDP.
- Hệ thống P2P: Triển khai Bảng băm phân tán (DHT) hoặc các công cụ cộng tác linh hoạt (như IPFS).
- Truyền phát nội dung nghe nhìn: Tận dụng UDP để truyền phát nội dung đến nhiều điểm cuối cùng lúc (truyền đa hướng), cho phép các trường hợp sử dụng như phát video phối hợp trên một mạng lưới ki-ốt bán lẻ.
- Khả năng của máy chủ và trình nghe: Định cấu hình IWA để hoạt động như một điểm cuối nhận cho các kết nối TCP hoặc gói dữ liệu UDP đến bằng cách sử dụng
TCPServerSockethoặcUDPSocketliên kết.
Điều kiện tiên quyết đối với Direct Sockets
Trước khi sử dụng Direct Sockets, bạn cần thiết lập một IWA hoạt động. Sau đó, bạn có thể tích hợp Direct Sockets vào các trang của mình.
Thêm chính sách về quyền
Để sử dụng Direct Sockets, bạn phải định cấu hình đối tượng permissions_policy trong tệp kê khai IWA. Bạn cần thêm khoá direct-sockets để bật API một cách rõ ràng. Ngoài ra, bạn phải thêm khoá cross-origin-isolated. Khoá này không dành riêng cho Direct Sockets, nhưng bắt buộc đối với tất cả các IWA và xác định xem tài liệu có thể truy cập vào các API yêu cầu tính năng cách ly trên nhiều nguồn gốc hay không.
{
"permissions_policy": {
"direct-sockets": ["self"],
"cross-origin-isolated": ["self"]
}
}
Khoá direct-sockets xác định xem các lệnh gọi đến new TCPSocket(...), new
TCPServerSocket(...) hoặc new UDPSocket(...) có được phép hay không. Nếu bạn không đặt chính sách này, các hàm khởi tạo này sẽ ngay lập tức từ chối bằng một NotAllowedError.
Triển khai TCPSocket
Các ứng dụng có thể yêu cầu kết nối TCP bằng cách tạo một thực thể TCPSocket.
Mở một kết nối
Để mở một kết nối, hãy sử dụng toán tử new và await lời hứa đã mở.
Hàm khởi tạo TCPSocket sẽ bắt đầu kết nối bằng cách sử dụng remoteAddress và remotePort đã chỉ định.
const remoteAddress = 'example.com';
const remotePort = 7;
// Configure options like keepAlive or buffering
const options = {
keepAlive: true,
keepAliveDelay: 720000
};
let tcpSocket = new TCPSocket(remoteAddress, remotePort, options);
// Wait for the connection to be established
let { readable, writable } = await tcpSocket.opened;
Đối tượng cấu hình không bắt buộc cho phép kiểm soát mạng một cách chi tiết; trong trường hợp cụ thể này, keepAliveDelay được đặt thành 720000 mili giây để duy trì kết nối trong thời gian không hoạt động. Nhà phát triển cũng có thể định cấu hình các thuộc tính khác tại đây, chẳng hạn như noDelay, vô hiệu hoá thuật toán Nagle để ngăn hệ thống xử lý hàng loạt các gói nhỏ (có thể giảm độ trễ) hoặc sendBufferSize và receiveBufferSize để quản lý thông lượng.
Trong phần cuối của đoạn mã trước đó, mã này chờ lời hứa đã mở, chỉ phân giải một lần sau khi quá trình bắt tay hoàn tất, trả về một đối tượng TCPSocketOpenInfo chứa các luồng có thể đọc và ghi cần thiết để truyền dữ liệu.
Đọc và ghi
Sau khi mở ổ cắm, hãy tương tác với ổ cắm đó bằng các giao diện Streams API tiêu chuẩn.
- Ghi: Luồng có thể ghi chấp nhận một
BufferSource(chẳng hạn nhưArrayBuffer). - Đọc: Luồng có thể đọc sẽ tạo ra dữ liệu
Uint8Array.
// Writing data
const writer = writable.getWriter();
const encoder = new TextEncoder();
await writer.write(encoder.encode("Hello Server"));
// Call when done
writer.releaseLock();
// Reading data
const reader = readable.getReader();
const { value, done } = await reader.read();
if (!done) {
const decoder = new TextDecoder();
console.log("Received:", decoder.decode(value));
}
// Call when done
reader.releaseLock();
Đọc tối ưu hoá bằng BYOB
Đối với các ứng dụng hiệu suất cao mà việc quản lý hoạt động phân bổ bộ nhớ là rất quan trọng, API này hỗ trợ hoạt động đọc "Bring Your Own Buffer" (BYOB). Thay vì để trình duyệt phân bổ một vùng đệm mới cho mọi khối dữ liệu nhận được, bạn có thể truyền một vùng đệm được phân bổ trước cho trình đọc. Điều này giúp giảm mức hao tổn khi thu gom rác bằng cách ghi dữ liệu trực tiếp vào bộ nhớ hiện có.
// 1. Get a BYOB reader explicitly
const reader = readable.getReader({ mode: 'byob' });
// 2. Allocate a reusable buffer (e.g., 4KB)
let buffer = new Uint8Array(4096);
// 3. Read directly into the existing buffer
const { value, done } = await reader.read(buffer);
if (!done) {
// 'value' is a view of the data written directly into your buffer
console.log("Bytes received:", value.byteLength);
}
reader.releaseLock();
Triển khai UDPSocket
Lớp UDPSocket cho phép giao tiếp UDP. Công cụ này hoạt động ở 2 chế độ riêng biệt, tuỳ thuộc vào cách bạn định cấu hình các lựa chọn.
Chế độ đã kết nối
Ở chế độ này, ổ cắm giao tiếp với một đích đến cụ thể. Điều này rất hữu ích cho các tác vụ tiêu chuẩn giữa máy khách và máy chủ.
// Connect to a specific remote host
let udpSocket = new UDPSocket({
remoteAddress: 'example.com',
remotePort: 7 });
let { readable, writable } = await udpSocket.opened;
Chế độ liên kết
Ở chế độ này, ổ cắm được liên kết với một điểm cuối IP cục bộ. Nó có thể nhận các gói dữ liệu từ các nguồn tuỳ ý và gửi đến các đích đến tuỳ ý. Thường được dùng cho các giao thức khám phá cục bộ hoặc hành vi giống như máy chủ.
// Bind to all interfaces (IPv6)
let udpSocket = new UDPSocket({
localAddress: '::'
// omitting localPort lets the OS pick one
});
// localPort will tell you the OS-selected port.
let { readable, writable, localPort } = await udpSocket.opened;
Xử lý thông báo UDP
Không giống như luồng byte TCP, luồng UDP xử lý các đối tượng UDPMessage, chứa dữ liệu và thông tin địa chỉ từ xa. Mã sau đây minh hoạ cách xử lý các thao tác Đầu vào/Đầu ra khi sử dụng UDPSocket ở "chế độ liên kết".
// Writing (Bound Mode requires specifying destination)
const writer = writable.getWriter();
await writer.write({
data: new TextEncoder().encode("Ping"),
remoteAddress: '192.168.1.50',
remotePort: 8080
});
// Reading
const reader = readable.getReader();
const { value } = await reader.read();
// value contains: { data, remoteAddress, remotePort }
console.log(`Received from ${value.remoteAddress}:`, value.data);
Không giống như "chế độ kết nối" (trong đó ổ cắm bị khoá với một thiết bị ngang hàng cụ thể), chế độ liên kết cho phép ổ cắm giao tiếp với các đích đến tuỳ ý. Do đó, khi ghi dữ liệu vào luồng có thể ghi, bạn phải truyền một đối tượng UDPMessage chỉ định rõ ràng remoteAddress và remotePort cho từng gói, hướng dẫn chính xác cho socket biết nơi định tuyến gói dữ liệu cụ thể đó. Tương tự, khi đọc từ luồng có thể đọc, giá trị trả về không chỉ bao gồm tải trọng dữ liệu mà còn bao gồm remoteAddress và remotePort của người gửi, cho phép ứng dụng của bạn xác định nguồn gốc của mọi gói đến.
Lưu ý: Khi sử dụng UDPSocket ở "chế độ kết nối", ổ cắm sẽ được khoá hiệu quả với một thiết bị ngang hàng cụ thể, giúp đơn giản hoá quy trình I/O. Ở chế độ này, các thuộc tính remoteAddress và remotePort sẽ không hoạt động khi ghi, vì đích đến đã được cố định. Tương tự, khi đọc tin nhắn, các thuộc tính này sẽ trả về giá trị rỗng, vì nguồn được đảm bảo là thiết bị ngang hàng đã kết nối.
Hỗ trợ truyền tin đa hướng
Đối với các trường hợp sử dụng như đồng bộ hoá việc phát video trên nhiều ki-ốt hoặc triển khai tính năng khám phá thiết bị cục bộ (ví dụ: mDNS), Direct Sockets hỗ trợ UDP truyền tin đa hướng. Điều này cho phép gửi tin nhắn đến địa chỉ "nhóm" và được tất cả người đăng ký trên mạng nhận, thay vì một người dùng cụ thể.
Quyền phát đa hướng
Để sử dụng các chức năng truyền tin đa hướng, bạn phải thêm quyền direct-sockets-multicast cụ thể vào tệp kê khai IWA. Quyền này khác với quyền ổ cắm trực tiếp tiêu chuẩn và cần thiết vì truyền tin đa hướng chỉ được dùng trong mạng riêng.
{
"permissions_policy": {
"direct-sockets": ["self"],
"direct-sockets-multicast": ["self"],
"direct-sockets-private": ["self"],
"cross-origin-isolated": ["self"]
}
}
Gửi gói dữ liệu đa hướng
Việc gửi đến một nhóm truyền tin đa hướng rất giống với "chế độ kết nối" UDP tiêu chuẩn, với việc bổ sung các lựa chọn cụ thể để kiểm soát hành vi của gói.
const MULTICAST_GROUP = '239.0.0.1';
const PORT = 12345;
const socket = new UDPSocket({
remoteAddress: MULTICAST_GROUP,
remotePort: PORT,
// Time To Live: How many router hops the packet can survive (default: 1)
multicastTimeToLive: 5,
// Loopback: Whether to receive your own packets (default: true)
multicastLoopback: true
});
const { writable } = await socket.opened;
// Write to the stream as usual...
Nhận các gói dữ liệu đa hướng
Để nhận lưu lượng truy cập truyền tin đa hướng, bạn phải mở một UDPSocket ở "chế độ liên kết" (thường liên kết với 0.0.0.0 hoặc ::), sau đó tham gia một nhóm cụ thể bằng cách sử dụng MulticastController. Bạn cũng có thể dùng lựa chọn multicastAllowAddressSharing (tương tự như SO_REUSEADDR trên Unix). Lựa chọn này rất cần thiết cho các giao thức khám phá thiết bị, trong đó nhiều ứng dụng trên cùng một thiết bị cần phải theo dõi cùng một cổng.
const socket = new UDPSocket({
localAddress: '0.0.0.0', // Listen on all interfaces
localPort: 12345,
multicastAllowAddressSharing: true // Allow multiple applications to bind to the same address / port pair.
});
// The open info contains the MulticastController
const { readable, multicastController } = await socket.opened;
// Join the group to start receiving packets
await multicastController.joinGroup('239.0.0.1');
const reader = readable.getReader();
// Read the stream...
const { value } = await reader.read();
console.log(`Received multicast from ${value.remoteAddress}`);
// When finished, you can leave the group (this is an optional, but recommended practice)
await multicastController.leaveGroup('239.0.0.1');
Tạo máy chủ
API này cũng hỗ trợ TCPServerSocket để chấp nhận các kết nối TCP đến, cho phép IWA của bạn hoạt động như một máy chủ cục bộ. Đoạn mã sau đây minh hoạ cách thiết lập một máy chủ TCP bằng giao diện TCPServerSocket.
// Listen on all interfaces (IPv6)
let tcpServerSocket = new TCPServerSocket('::');
// Accept connections via the readable stream
let { readable } = await tcpServerSocket.opened;
let reader = readable.getReader();
// Wait for a client to connect
let { value: clientSocket } = await reader.read();
// 'clientSocket' is a standard TCPSocket you can now read/write to
Bằng cách khởi tạo lớp bằng địa chỉ '::', máy chủ sẽ liên kết với tất cả các giao diện mạng IPv6 hiện có để theo dõi các yêu cầu đến. Không giống như các API máy chủ truyền thống dựa trên lệnh gọi lại, API này sử dụng mẫu Streams API của web: các kết nối đến được phân phối dưới dạng ReadableStream. Khi bạn gọi reader.read(), ứng dụng sẽ chờ và chấp nhận kết nối tiếp theo từ hàng đợi, phân giải thành một giá trị là thực thể TCPSocket có đầy đủ chức năng, sẵn sàng cho giao tiếp hai chiều với khách hàng cụ thể đó.
Gỡ lỗi Direct Sockets bằng Công cụ của Chrome cho nhà phát triển
Từ Chrome 138, bạn có thể gỡ lỗi lưu lượng truy cập Direct Sockets ngay trong bảng điều khiển Mạng trong Công cụ của Chrome cho nhà phát triển, giúp bạn không cần dùng đến các trình theo dõi gói bên ngoài. Công cụ này cho phép bạn theo dõi các kết nối TCPSocket cũng như lưu lượng truy cập UDPSocket (ở cả chế độ liên kết và kết nối) cùng với các yêu cầu HTTP tiêu chuẩn.
Cách kiểm tra hoạt động mạng của ứng dụng:
- Mở bảng điều khiển Mạng trong Công cụ của Chrome cho nhà phát triển.
- Tìm và chọn kết nối ổ cắm trong bảng yêu cầu.
- Mở thẻ Messages (Tin nhắn) để xem nhật ký tất cả dữ liệu đã truyền và nhận.

Chế độ xem này cung cấp một Trình xem thập lục phân, cho phép bạn kiểm tra tải trọng nhị phân thô của các thông báo TCP và UDP, đảm bảo việc triển khai giao thức của bạn hoàn toàn chính xác.
Bản minh hoạ
IWA Kitchen Sink có một ứng dụng với nhiều thẻ, mỗi thẻ minh hoạ một API IWA khác nhau, chẳng hạn như Direct Sockets, Controlled Frame và nhiều API khác.
Ngoài ra, bản minh hoạ ứng dụng telnet chứa một Ứng dụng web riêng biệt cho phép người dùng kết nối với máy chủ TCP/IP thông qua một thiết bị đầu cuối tương tác. Nói cách khác, đây là một ứng dụng Telnet.
Kết luận
Direct Sockets API giúp khắc phục một khoảng trống chức năng quan trọng bằng cách cho phép các ứng dụng web xử lý các giao thức mạng thô mà trước đây không thể hỗ trợ nếu không có trình bao bọc gốc. Nó không chỉ đơn thuần là khả năng kết nối máy khách; với TCPServerSocket, các ứng dụng có thể lắng nghe các kết nối đến, trong khi UDPSocket cung cấp các chế độ linh hoạt cho cả giao tiếp ngang hàng và khám phá mạng cục bộ.
Bằng cách cung cấp các chức năng TCP và UDP thô này thông qua Streams API hiện đại, giờ đây, bạn có thể tạo các chế độ triển khai đầy đủ tính năng của các giao thức cũ (chẳng hạn như SSH, RDP hoặc các tiêu chuẩn IoT tuỳ chỉnh) ngay trong JavaScript. Vì API này cấp quyền truy cập mạng ở cấp thấp, nên nó có ý nghĩa bảo mật đáng kể. Do đó, API này bị hạn chế đối với Ứng dụng web tách biệt (IWA), đảm bảo rằng chỉ những ứng dụng đáng tin cậy, được cài đặt rõ ràng và thực thi các chính sách bảo mật nghiêm ngặt mới được cấp quyền này. Sự cân bằng này cho phép bạn tạo các ứng dụng mạnh mẽ, lấy thiết bị làm trung tâm trong khi vẫn duy trì sự an toàn mà người dùng mong đợi từ nền tảng web.