运行时包管理器打包器测试运行器代码检查格式化指南参考博客反馈

TCP

使用 Bun 原生 TCP API 实现数据库客户端、游戏服务器等对性能敏感的系统,或任何需要通过 TCP(而非 HTTP)进行通信的应用

Bun 的 TCP API 属于底层 API,面向库作者和高级使用场景。

启动服务器(Bun.listen())

使用 Bun.listen 启动 TCP 服务器:

server.ts
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 那样,为每个套接字分别分配回调。

server.ts
Bun.listen({
  hostname: "localhost",
  port: 8080,
  socket: {
    open(socket) {},
    data(socket, data) {},
    drain(socket) {},
    close(socket, error) {},
    error(socket, error) {},
  },
});

对于性能敏感的服务器,为每个连接分配监听器会造成显著的垃圾回收压力并增加内存使用。相比之下,Bun 仅为每种事件分配一个处理函数,并在所有连接间共享。这是一个小优化,但积少成多。

在 open 处理函数中为套接字附加上下文数据。

server.ts
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 对象。

server.ts
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 或这些类型组成的数组。

server.ts
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 接口的服务器。

server.ts
const server = Bun.listen({
  /* 配置 */
});

// 停止监听
// 参数表示是否关闭所有活动连接
server.stop(true);

// 让 Bun 进程即便服务器仍在监听也能退出
server.unref();

创建连接(Bun.connect())

使用 Bun.connect 连接到 TCP 服务器。通过 hostname 和 port 指定服务器。TCP 客户端可以定义与 Bun.listen 相同的一组处理器,此外还可以定义一些客户端专用的处理器。

server.ts
// 客户端
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。

TypeScript
// 客户端
const socket = await Bun.connect({
  // ... 配置
  tls: true, // [!code ++]
});

热重载

你可以使用新的处理器对 TCP 服务器和套接字进行热重载。

server.ts
const server = Bun.listen({
  /* 配置 */
});

// 为所有活动的服务器端连接重新加载处理器
server.reload({
  socket: {
    data() {
      // 新的 'data' 处理器
    },
  },
});
client.ts
const socket = await Bun.connect({
  /* 配置 */
});

socket.reload({
  data() {
    // 新的 'data' 处理器
  },
});

缓冲

Bun 中的 TCP 套接字不会缓冲数据,因此对性能敏感的代码应自行缓冲写入。例如:

TypeScript
socket.write("h");
socket.write("e");
socket.write("l");
socket.write("l");
socket.write("o");

性能会明显差于:

TypeScript
socket.write("hello");

要缓冲写入,请将 Bun 的 ArrayBufferSink 与 {stream: true} 选项结合使用:

server.ts
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));
  }
});