跳到正文
Cloudflare Blog· Mar Witek·· 2026-08-03AI 评分60

Cloudflare Workers 与 Containers 现已支持入站 TCP 连接和 gRPC

Cloudflare Workers and Containers now support inbound TCP connections and gRPC

AI 导读

Cloudflare 在 Agents Week 期间推出 Workers 运行时新处理器 connect(socket),可直接接受 Spectrum 提供的入站 TCP 套接字,并支持将套接字转发至 Durable Objects 或 Containers,实现全双工通信。

正文 · AI 翻译

AI 正在改变人与计算机的交互方式,而语音正成为这一变革中日益重要的一环。实时助手、AI 驱动的听写以及其他语音接口,都需要客户端、模型和支撑服务之间具备低延迟通信能力。许多开发者使用 gRPC——一种构建于 HTTP/2 和 TCP 之上的远程过程调用(RPC)框架——来搭建这类基础设施。

自 2017 年 Workers 发布以来,我们一直在扩展其能力,包括增加发起出站 TCP 连接的功能,以及一套基于 Cap'n Proto 构建的 JavaScript 原生 RPC 系统。因此,作为 Agents Week 的一部分,我们正从另一个方向扩展 Workers:支持入站 TCP 连接,并新增在 Cloudflare 上运行 gRPC 应用的方式。

今天,我们宣布:

  • connect(socket)——Workers 运行时中新增的一个处理器,让你的 Worker 能够直接接受由 Spectrum(Cloudflare 面向非 HTTP 流量的入站代理)提供的入站 TCP socket。
  • 来自 Cloudflare Containers 的全双工、双向 gRPC——将 socket 从你的 Worker 转发到运行在容器中的 gRPC 服务器。
  • Workers 可以对外提供 unary 和 server-streaming 类型的 gRPC API,也可以调用 gRPC 服务器——你使用 gRPC-web 编写代码,Cloudflare 会自动将入站和出站请求转换为 gRPC。

我们正以私有测试版的形式推出这一功能——你可以在此处注册。

下面我们来逐一深入介绍。

从你的 Worker 到 Durable Objects 和 Containers 的 connect(socket)

Workers 运行时现在提供了一个 connect() 处理器,用于接受一个可读可写的 socket:

export default {
	async connect(socket): Promise<void> {
		const writer = socket.writable.getWriter();
		await writer.write(new TextEncoder().encode("Hello, world!\n"));
		await writer.close();
	},
} satisfies ExportedHandler;

你可以将这个 socket 从一个 Worker 传递给另一个 Worker,或从 Worker 传递给 Durable Object。这让你能够控制入站 TCP 连接的路由去向:

import { DurableObject } from "cloudflare:workers";

export class SocketDurableObject extends DurableObject<Env> {
	async connect(socket: Socket): Promise<void> {
		// Echo bytes from inside the Durable Object
		await socket.readable.pipeTo(socket.writable);
	}
}

export default {
	async connect(socket, env): Promise<void> {
		const stub = env.SOCKET_DO.getByName("my-server");
		const durableObjectSocket = stub.connect("host:port");

		await Promise.all([
			socket.readable.pipeTo(durableObjectSocket.writable),
			durableObjectSocket.readable.pipeTo(socket.writable),
		]);
	},
} satisfies ExportedHandler<Env>;

你可以将 socket 从 Durable Object 传递给其对应的 Container:

import { DurableObject } from "cloudflare:workers";

export class SocketContainer extends DurableObject<Env> {
	constructor(ctx: DurableObjectState, env: Env) {
		super(ctx, env);
		this.ctx.container!.start();
	}

	async connect(socket: Socket): Promise<void> {
		const containerSocket = this.ctx.container!
			.getTcpPort(8080)
			.connect("10.0.0.1:8080");

		await containerSocket.opened;

		await Promise.all([
			socket.readable.pipeTo(containerSocket.writable),
			containerSocket.readable.pipeTo(socket.writable),
		]);
	}
}

然后在容器中处理该 socket:

# server.py
import socketserver

class Handler(socketserver.BaseRequestHandler):
    def handle(self):
        while data := self.request.recv(64 * 1024):
            self.request.sendall(b"Echo: " + data)

class Server(socketserver.ThreadingTCPServer):
    allow_reuse_address = True
    daemon_threads = True

with Server(("0.0.0.0", 8080), Handler) as server:
    server.serve_forever()

这让你能够完全掌控从客户端到运行在 Cloudflare 容器中服务器的整条路径,为客户端与服务器之间运行任意程序、任意语言、任意基于 TCP 的协议的全双工通信打开了大门。

为了向客户端暴露原始 TCP 套接字,我们推出了一种新型 Spectrum 应用,你可以指定一个 Worker,将传入的 TCP 连接路由到该 Worker。Spectrum 是 Cloudflare 针对非 HTTP 流量的入站代理,它让 Cloudflare 能够部署在任何 TCP 或 UDP 应用的前端。

来自 Cloudflare Containers 的双向 gRPC

gRPC 是一种成熟且广受欢迎的远程过程调用(RPC)框架,最初由 Google 在近 10 年前发布,如今已广泛应用于移动应用、分布式系统,以及最近兴起的——语音 AI 应用。

实时语音 AI 应用要求低延迟,并且客户端和服务器需要能够通过单一、持久的连接相互发送消息。WebSockets 和 Durable Objects 非常适合这一场景,Cloudflare Agents SDK 提供了 @cloudflare/voice 来简化这一过程。但市面上仍有大量软件使用 gRPC 进行实时客户端-服务器通信。

利用上述 API,你现在可以将用任何语言编写的 gRPC 服务器部署到 Cloudflare,并完全支持客户端与服务器之间的双向流式传输。这让你能够利用 Cloudflare 遍布 330+ 个地点的网络,在比其他任何地方都更靠近客户端的位置处理请求。我们对这为低延迟语音和同地推理所开辟的可能性感到兴奋。

例如,下面是一个极简的 gRPC 服务器,它会将收到的消息回显给客户端:

package main

import (
	"io"
	"log"
	"net"

	pb "example/proto"
	"google.golang.org/grpc"
)

type server struct {
	pb.UnimplementedByteStreamServer
}

func (server) Chat(stream pb.ByteStream_ChatServer) error {
	if err := stream.Send(&pb.ByteChunk{
		Payload: []byte("connected\n"),
	}); err != nil {
		return err
	}

	for {
		message, err := stream.Recv()

		if err == io.EOF {
			return stream.Send(&pb.ByteChunk{
				Payload: []byte("goodbye\n"),
			})
		}
		if err != nil {
			return err
		}

		if err := stream.Send(&pb.ByteChunk{
			Payload: append([]byte("echo: "), message.Payload...),
		}); err != nil {
			return err
		}
	}
}

func main() {
	listener, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatal(err)
	}

	grpcServer := grpc.NewServer()
	pb.RegisterByteStreamServer(grpcServer, &server{})

	log.Println("gRPC server listening on :50051")
	log.Fatal(grpcServer.Serve(listener))
}

有了这个,几乎没有任何基于 gRPC 的应用是你无法部署到 Cloudflare 的,无论它使用什么语言或依赖什么库。但如果你需要做一些更简单的事情,比如只是运行一个基础的 gRPC 服务器,或者从 Worker 连接到运行在其他地方的 gRPC 服务器,那该怎么办?

Workers 作为 gRPC 服务器和客户端,支持 gRPC 到 gRPC-web 的转换——无需容器

gRPC-web 是 gRPC 的浏览器兼容版本。Web 浏览器不暴露 gRPC 所需的底层 HTTP/2 特性,而且浏览器中也没有内置的原始 TCP Socket API——这正是 WebSocket API 存在的原因,也是 Workers 自 2021 年起就支持 WebSockets 的原因。

HTTP/2 会将每个请求和响应拆分成称为帧的小型二进制消息。这是单个 HTTP/2 或 HTTP/3 连接能够实现多路复用的核心机制——多个请求可以在同一条连接上交错传输。每个帧都带有流 ID,使接收方能够将其重新组装成正确的请求或响应。gRPC 依赖这种流级控制来实现高效的流式传输、取消、流量控制和尾部元数据。

fetch() 等 Web 平台 API 并不提供这种控制能力。那么,我们如何让 Cloudflare Workers 能够简单便捷地使用 gRPC——同时客户端无需做任何改动?我们将入站的 gRPC 转换为 gRPC-web,并将出站的 gRPC-web 转换回 gRPC。

实际上,自 2020 年起,我们就在 Cloudflare 的反向代理中使用了 gRPC-web,当时我们在 Cloudflare 博客上发表了关于 gRPC 之路的文章。我们将请求转换为 HTTP/1.1,以便能够检查消息内容,并让 gRPC 应用受益于 Cloudflare 的安全功能,例如 WAF 规则和 Bot Management。

现在,在私有测试版阶段,随后将向所有人开放,我们正在扩展这一能力:只要给定一个像这样的 Protocol Buffer(protobuf)定义文件:

syntax = "proto3";

package hello;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

您只需几行代码,就可以使用 @connectrpc/connect 开源包,在 Worker 中编写一个一元 gRPC 服务器:

import { createConnectRouter } from "@connectrpc/connect";
import {
  universalServerRequestFromFetch,
  universalServerResponseToFetch,
} from "@connectrpc/connect/protocol";
import { Greeter } from "./gen/hello_pb";

const router = createConnectRouter();

router.service(Greeter, {
  sayHello: ({ name }) => ({ message: `Hello, ${name}!` }),
});

const handlers = new Map(
  router.handlers.map((handler) => [handler.requestPath, handler]),
);

export default {
  async fetch(request: Request): Promise<Response> {
    const handler = handlers.get(new URL(request.url).pathname);
    return universalServerResponseToFetch(
      await handler(universalServerRequestFromFetch(request, {})),
    );
  },
} satisfies ExportedHandler;

您也可以使用 @connectrpc/connect 内置的客户端,以这种方式向外部 gRPC 服务器发起出站请求:

import { createClient } from "@connectrpc/connect";
import { createGrpcWebTransport } from "@connectrpc/connect-web";
import { Greeter } from "./gen/hello_pb";

const client = createClient(
  Greeter,
  createGrpcWebTransport({
    baseUrl: "https://grpc.example.com",
    fetch: (input, init) =>
      fetch(input, { ...init, redirect: "manual" }),
  }),
);

export default {
  async fetch(): Promise<Response> {
    const reply = await client.sayHello({ name: "Workers" });
    return Response.json(reply);
  },
} satisfies ExportedHandler;

您的代码使用 gRPC-web,但当它与外部世界通信时,会自动转换为 gRPC。这意味着您已经依赖的客户端和服务器无需更改。例如,您可以:

  • 为使用 gRPC 的移动应用提供 gRPC 后端——许多移动应用已经使用 gRPC 来减少网络负载、更高效地序列化数据,并生成强类型客户端库。现在,您可以在 Workers 上为移动应用构建后端服务器,同时仍然使用成熟的 gRPC 原生库,如 grpc-swift-2 和 grpc-kotlin。
  • 在现有 gRPC 后端前放置一个 Worker —— 许多开发者已经在现有 REST API 前放置 Worker,以便将性能关键型工作迁移到更靠近用户的位置,或逐步将状态迁移到 Durable Objects。现在,你也可以对现有的 gRPC 后端执行此操作,或者构建从现有 gRPC 后端获取数据的新 API 和服务。

Cloudflare 上 Socket Workers 和 gRPC 的下一步计划

我们将在私有测试版中推出本文介绍的所有内容——你可以在此处注册。

在 Cloudflare,我们使用 Cap'n Proto、Cap'n Web 以及内置于 Cloudflare Workers 的 JavaScript 原生 RPC 系统,而不是 gRPC。当我们发布功能时,我们总是力求自己先使用它们。因此,在这种情况下,我们希望首先与一小部分使用 gRPC 的开发者密切合作,确保我们做到尽善尽美,然后再向所有人开放。

更广泛地说,我们很高兴能继续突破 Workers 平台可承载的流量类型的边界,超越 TCP,进入基于 UDP 的协议。请继续告诉我们你想在 Workers 上构建什么,我们将继续突破可能性的边界。

来源:Cloudflare Blog · blog.cloudflare.com

相关事件