Bun

TCP 套接字

使用 Bun 的原生 TCP API 來實作效能敏感的系統,例如資料庫客戶端、遊戲伺服器或任何需要透過 TCP(而非 HTTP)通訊的系統。這是一個低階 API,專門為函式庫作者和進階使用案例而設計。

啟動伺服器 (Bun.listen())

使用 Bun.listen 啟動 TCP 伺服器

Bun.listen({
  hostname: "localhost",
  port: 8080,
  socket: {
    data(socket, data) {}, // message received from client
    open(socket) {}, // socket opened
    close(socket) {}, // socket closed
    drain(socket) {}, // socket ready for more data
    error(socket, error) {}, // error handler
  },
});

專為速度而設計的 API

可以在 open 處理常式中將內容資料附加到 socket。

type SocketData = { sessionId: string };

Bun.listen<SocketData>({
  hostname: "localhost",
  port: 8080,
  socket: {
    data(socket, data) {
      socket.write(`${socket.data.sessionId}: ack`);
    },
    open(socket) {
      socket.data = { sessionId: "abcd" };
    },
  },
});

若要啟用 TLS,請傳遞包含 keycert 欄位的 tls 物件。

Bun.listen({
  hostname: "localhost",
  port: 8080,
  socket: {
    data(socket, data) {},
  },
  tls: {
    // can be string, BunFile, TypedArray, Buffer, or array thereof
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
  },
});

keycert 欄位預期會是 TLS 金鑰和憑證的內容。這可以是字串、BunFileTypedArrayBuffer

Bun.listen({
  // ...
  tls: {
    // BunFile
    key: Bun.file("./key.pem"),
    // Buffer
    key: fs.readFileSync("./key.pem"),
    // string
    key: fs.readFileSync("./key.pem", "utf8"),
    // array of above
    key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")],
  },
});

Bun.listen 的結果是一個符合 TCPSocket 介面的伺服器。

const server = Bun.listen({
  /* config*/
});

// stop listening
// parameter determines whether active connections are closed
server.stop(true);

// let Bun process exit even if server is still listening
server.unref();

建立連線 (Bun.connect())

使用 Bun.connect 連線到 TCP 伺服器。使用 hostnameport 指定要連線的伺服器。TCP 伺服器可以定義與 Bun.listen 相同的處理常式,以及幾個特定於伺服器的處理常式。

// The client
const socket = await Bun.connect({
  hostname: "localhost",
  port: 8080,

  socket: {
    data(socket, data) {},
    open(socket) {},
    close(socket) {},
    drain(socket) {},
    error(socket, error) {},

    // client-specific handlers
    connectError(socket, error) {}, // connection failed
    end(socket) {}, // connection closed by server
    timeout(socket) {}, // connection timed out
  },
});

若要要求 TLS,請指定 tls: true

// The client
const socket = await Bun.connect({
  // ... config
  tls: true,
});

熱重載

TCP 伺服器和 Socket 都可以使用新的處理常式進行熱重載。

伺服器
客戶端
伺服器
const server = Bun.listen({ /* config */ })

// reloads handlers for all active server-side sockets
server.reload({
  socket: {
    data(){
      // new 'data' handler
    }
  }
})
客戶端
const socket = await Bun.connect({ /* config */ })
socket.reload({
  data(){
    // new 'data' handler
  }
})

緩衝

目前,Bun 中的 TCP Socket 沒有緩衝資料。對於效能敏感的程式碼,仔細考慮緩衝非常重要。例如,這個

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, 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) {
    // put it back in the sink if the socket is full
    sink.write(data.subarray(wrote));
  }
});

封塞 — 支援封塞已在計畫中,但在此同時,必須使用 drain 處理常式手動管理反壓力。