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, error) {}, // socket closed
    drain(socket) {}, // socket ready for more data
    error(socket, error) {}, // error handler
  },
});

为速度而设计的 API

上下文数据可以在 `open` 处理程序中附加到套接字。

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,请传递一个包含 `key` 和 `cert` 字段的 `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"),
  },
});

`key` 和 `cert` 字段需要 TLS 密钥和证书的*内容*。这可以是字符串、`BunFile`、`TypedArray` 或 `Buffer`。

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 服务器。使用 `hostname` 和 `port` 指定要连接的服务器。TCP 客户端可以定义与 `Bun.listen` 相同的处理程序集,外加一些客户端特定的处理程序。

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

  socket: {
    data(socket, data) {},
    open(socket) {},
    close(socket, error) {},
    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 服务器和套接字都可以使用新的处理程序进行热重载。

服务器
客户端
服务器
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.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));
  }
});

Corking — 计划支持 corking,但在那之前,必须使用 `drain` 处理程序手动管理背压。