TCP
使用 Bun 原生 TCP API 實現資料庫客戶端、遊戲伺服器等對效能敏感的系統,或任何需要通過 TCP(而非 HTTP)進行通訊的應用
Bun 的 TCP API 屬於底層 API,面向庫作者和高階使用場景。
啟動伺服器(Bun.listen())
使用 Bun.listen 啟動 TCP 伺服器:
Bun.listen({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {}, // 收到來自客戶端的訊息
open(socket) {}, // 連線開啟
close(socket, error) {}, // 連線關閉
drain(socket) {}, // 連線可寫入更多資料
error(socket, error) {}, // 錯誤處理
},
});
為速度設計的 API
在 Bun 中,你可以為每個伺服器宣告一組處理函式,而不是像 Node.js 的 EventEmitter 或符合 Web 標準的 WebSocket API 那樣,為每個套接字分別分配回撥。
Bun.listen({
hostname: "localhost",
port: 8080,
socket: {
open(socket) {},
data(socket, data) {},
drain(socket) {},
close(socket, error) {},
error(socket, error) {},
},
});
對於效能敏感的伺服器,為每個連線分配監聽器會造成顯著的垃圾回收壓力並增加記憶體使用。相比之下,Bun 僅為每種事件分配一個處理函式,並在所有連線間共享。這是一個小最佳化,但積少成多。
在 open 處理函式中為套接字附加上下文資料。
type SocketData = { sessionId: string };
Bun.listen<SocketData>({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {
socket.write(`${socket.data.sessionId}: ack`); // [!code ++]
},
open(socket) {
socket.data = { sessionId: "abcd" }; // [!code ++]
},
},
});
要啟用 TLS,傳入包含 key 和 cert 欄位的 tls 物件。
Bun.listen({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {},
},
tls: {
// 可為字串、BunFile、TypedArray、Buffer 或它們的陣列
key: Bun.file("./key.pem"), // [!code ++]
cert: Bun.file("./cert.pem"), // [!code ++]
},
});
key 和 cert 欄位需要提供 TLS 金鑰和證書的_內容_。它們可以是字串、BunFile、TypedArray、Buffer 或這些型別組成的陣列。
Bun.listen({
// ...
tls: {
key: Bun.file("./key.pem"), // BunFile
key: fs.readFileSync("./key.pem"), // Buffer
key: fs.readFileSync("./key.pem", "utf8"), // 字串
key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")], // 上述型別的陣列
},
});
Bun.listen 傳回一個符合 TCPSocketListener 介面的伺服器。
const server = Bun.listen({
/* 設定 */
});
// 停止監聽
// 參數列示是否關閉所有活動連線
server.stop(true);
// 讓 Bun 行程即便伺服器仍在監聽也能退出
server.unref();
建立連線(Bun.connect())
使用 Bun.connect 連線到 TCP 伺服器。通過 hostname 和 port 指定伺服器。TCP 客戶端可以定義與 Bun.listen 相同的一組處理器,此外還可以定義一些客戶端專用的處理器。
// 客戶端
const socket = await Bun.connect({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {},
open(socket) {},
close(socket, error) {},
drain(socket) {},
error(socket, error) {},
// 客戶端專用處理器
connectError(socket, error) {}, // 連線失敗
end(socket) {}, // 伺服器關閉連線
timeout(socket) {}, // 連線超時
},
});
要啟用 TLS,設定 tls: true。
// 客戶端
const socket = await Bun.connect({
// ... 設定
tls: true, // [!code ++]
});
熱過載
你可以使用新的處理器對 TCP 伺服器和套接字進行熱過載。
const server = Bun.listen({
/* 設定 */
});
// 為所有活動的伺服器端連線重新載入處理器
server.reload({
socket: {
data() {
// 新的 'data' 處理器
},
},
});
const socket = await Bun.connect({
/* 設定 */
});
socket.reload({
data() {
// 新的 'data' 處理器
},
});
緩衝
Bun 中的 TCP 套接字不會緩衝資料,因此對效能敏感的程式碼應自行緩衝寫入。例如:
socket.write("h");
socket.write("e");
socket.write("l");
socket.write("l");
socket.write("o");
效能會明顯差於:
socket.write("hello");
要緩衝寫入,請將 Bun 的 ArrayBufferSink 與 {stream: true} 選項結合使用:
import { ArrayBufferSink } from "bun";
const sink = new ArrayBufferSink();
sink.start({
stream: true, // [!code ++]
highWaterMark: 1024,
});
sink.write("h");
sink.write("e");
sink.write("l");
sink.write("l");
sink.write("o");
queueMicrotask(() => {
const data = sink.flush();
const wrote = socket.write(data);
if (wrote < data.byteLength) {
// 如果連線滿了,剩餘資料放回緩衝區
sink.write(data.subarray(wrote));
}
});