☰
Cloudflare Workers TCP Sockets API 完全指南:用 cloudflare:sockets 打通私有网络与自定义协议
2026/10/10 8:53:32 网站建设 项目流程

【免费下载链接】autoskills

One command. Your entire AI skill stack. Installed.

项目地址:https://gitcode.com/gh_mirrors/au/autoskills
点击查看免费下载

cloudflare:sockets是 Cloudflare Workers 运行时提供的底层 TCP Socket API,它允许 Worker 发起出站 TCP 连接,直连 AWS、Azure、GCP、本地数据中心或任意私有网络中的资源,是 Workers 私有网络(VPC)连通性的核心通道。本文以仓库中 workers-vpc/api.md 为骨架,融合 patterns.md、configuration.md 与 gotchas.md,完整讲解connect()函数签名、Socket 接口、TLS/StartTLS 协商、Wrangler 配置、Cloudflare Tunnel 集成,以及重试、超时、SSRF 防护、连接池等生产级实战模式。读完本文,你将能够用 TCP Sockets 实现 Redis RESP、MQTT、SSH 等非 HTTP 协议的 Worker 客户端,并安全地接入私有网络。

一、何时选择 TCP Sockets

在 workers-vpc/README.md 中给出了一张技术选型决策表,TCP Sockets 并非万能方案,应按下表判断:

需求推荐方案理由
HTTP/HTTPS 私有 APIVPC Services(beta,另见文档)SSRF 安全、声明式绑定
PostgreSQL/MySQL 数据库Hyperdrive内置连接池与缓存
自定义 TCP 协议(SSH、MQTT、私有协议)TCP Sockets(本文主题)完全掌控线上协议
追求最低延迟的简单 HTTPTCP Sockets + Smart Placement手动优化路径
将本地/私有服务暴露到公网(入站)Cloudflare Tunnel非 Worker 专属能力

适合使用 TCP Sockets 的场景:需要对线上协议有直接控制权(如 Postgres wire protocol、SSH、Redis RESP);协议本身不是 HTTP(MQTT、SMTP、自定义二进制协议);需要 StartTLS 或自定义 TLS 协商;需要通过 TCP 流式传输二进制数据。

不适合的场景:仅需 HTTP/HTTPS(应使用fetch()或 VPC Services);需要 PostgreSQL/MySQL(用 Hyperdrive 获得连接池);需要 WebSocket(用 Workers 原生 WebSocket)。

二、核心函数:connect()

TCP Sockets API 的唯一入口是connect(),它创建一个到指定地址的出站 TCP 连接:

function connect( address: SocketAddress, options?: SocketOptions ): Socket

2.1SocketAddress:目标地址

interface SocketAddress { hostname: string; // DNS 主机名或 IP 地址 port: number; // TCP 端口(1-65535,排除被封锁的端口) }
字段类型说明示例
hostnamestring目标主机名或 IP"db.internal.net"、"10.0.1.50"
portnumberTCP 端口号5432、443、22

DNS 名称在连接时解析。IPv4、IPv6 以及私有 IP(10.x、172.16.x、192.168.x)均受支持。注意端口范围受平台限制:端口 25(SMTP)等被封锁,详见 gotchas.md。

2.2SocketOptions:连接选项

interface SocketOptions { secureTransport?: "off" | "on" | "starttls"; allowHalfOpen?: boolean; }
字段类型默认值说明
secureTransport"off" \| "on" \| "starttls""off"TLS 模式
allowHalfOpenbooleanfalse是否允许半关闭连接

secureTransport三种模式:

模式行为适用场景
"off"纯 TCP,无加密测试、内部可信网络
"on"立即进行 TLS 握手HTTPS、安全数据库、SSH
"starttls"以明文开始,之后用startTls()升级Postgres、SMTP、IMAP

allowHalfOpen语义:为false(默认)时,关闭读取流会自动关闭写入流;为true时,读写两个方向的流相互独立,适合需要半关闭语义的自定义协议。

三、Socket 接口详解

connect()返回的Socket对象封装了读写流与连接状态:

interface Socket { // 流 readable: ReadableStream<Uint8Array>; writable: WritableStream<Uint8Array>; // 连接状态 opened: Promise<SocketInfo>; closed: Promise<void>; // 方法 close(): Promise<void>; startTls(): Socket; }

3.1readable: ReadableStream<Uint8Array>

用于从 socket 读取数据的流,通过getReader()消费:

const reader = socket.readable.getReader(); const { done, value } = await reader.read(); // 读取一个数据块

注意:一次read()并不保证拿到全部数据,服务端数据可能分多个 chunk 到达。需要循环读取直到done === true,实战写法见本文第五章。

3.2writable: WritableStream<Uint8Array>

用于向 socket 写入数据的流,通过getWriter()发送:

const writer = socket.writable.getWriter(); await writer.write(new TextEncoder().encode("HELLO\r\n")); await writer.close();

writer.close()会通知对端本方向数据发送完毕,配合allowHalfOpen可以构建"写后等待响应"的请求-响应模式。

3.3opened: Promise<SocketInfo>

连接成功时 resolve、失败时 reject 的 Promise,是判断连接是否建立的唯一可靠方式:

interface SocketInfo { remoteAddress?: string; // 可能为 undefined localAddress?: string; // 可能为 undefined } try { const info = await socket.opened; } catch (error) { // 连接失败 }

SocketInfo中的remoteAddress常用于调试日志(见 gotchas.md 的 Debugging Tips)。

3.4closed: Promise<void>

双向完全关闭后 resolve 的 Promise,可用于等待连接彻底结束、释放资源。

3.5close(): Promise<void>

优雅关闭 socket,会等待未完成的写入完成:

const socket = connect({ hostname: "api.internal", port: 443 }); try { // 使用 socket } finally { await socket.close(); // 务必在 finally 中调用 }

最佳实践:任何代码路径下都要在finally中调用close(),否则会造成资源泄漏。

3.6startTls(): Socket

将连接升级为 TLS。仅当创建时指定了secureTransport: "starttls"才可用,且升级后必须使用返回的新 socket,而不是原来的 socket:

const socket = connect( { hostname: "db.internal", port: 5432 }, { secureTransport: "starttls" } ); // 发送协议特定的 StartTLS 命令 const writer = socket.writable.getWriter(); await writer.write(new TextEncoder().encode("STARTTLS\r\n")); // 升级到 TLS —— 使用返回的 socket,而非原 socket const secureSocket = socket.startTls(); const secureWriter = secureSocket.writable.getWriter();

升级时机很关键:必须等待服务端对 STARTTLS 命令返回 OK 之后再调用startTls(),过早调用会导致握手失败(见第六章"StartTLS 时机")。

四、完整示例:一次请求-响应往返

将以上要素组合起来,就是一个可在 Worker 中直接部署的最小完整实现:

import { connect } from 'cloudflare:sockets'; export default { async fetch(req: Request): Promise<Response> { const socket = connect({ hostname: "echo.example.com", port: 7 }, { secureTransport: "on" }); try { await socket.opened; const writer = socket.writable.getWriter(); await writer.write(new TextEncoder().encode("Hello, TCP!\n")); await writer.close(); const reader = socket.readable.getReader(); const { value } = await reader.read(); return new Response(value); } finally { await socket.close(); } } };

流程拆解:connect()建连 →await socket.opened等待连接就绪 → 写入请求并关闭写入流 → 读取响应 →finally中关闭 socket。这个结构是后续所有实战模式的公共骨架。

五、实战模式:从读取到多协议网关

patterns.md 给出了可直接复用的生产级代码。

5.1 读取全部数据

单次read()可能只拿到部分 chunk,正确做法是循环累积:

async function readAll(socket: Socket): Promise<Uint8Array> { const reader = socket.readable.getReader(); const chunks: Uint8Array[] = []; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); } const total = chunks.reduce((sum, c) => sum + c.length, 0); const result = new Uint8Array(total); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } return result; }

5.2 流式响应:TCP 直通 HTTP

不需要等全部数据,直接把socket.readable作为 HTTP Response body,实现 TCP→HTTP 的流式管道:

const socket = connect({ hostname: "stream.internal", port: 9000 }, { secureTransport: "on" }); const writer = socket.writable.getWriter(); await writer.write(new TextEncoder().encode("STREAM\n")); await writer.close(); return new Response(socket.readable);

5.3 协议示例:Redis RESP 与 MQTT

Redis RESP——按 RESP 协议手工构造命令帧:

// 发送: *2\r\n$3\r\nGET\r\n$<keylen>\r\n<key>\r\n // 接收: $<len>\r\n<data>\r\n 或 $-1\r\n(表示 null) const socket = connect({ hostname: "redis.internal", port: 6379 }); const writer = socket.writable.getWriter(); await writer.write(new TextEncoder().encode(`*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n`));

MQTT——构造 CONNECT/PUBLISH 控制报文:

const socket = connect({ hostname: "mqtt.broker", port: 1883 }); const writer = socket.writable.getWriter(); // CONNECT: 0x10 <len> 0x00 0x04 "MQTT" 0x04 <flags> ... // PUBLISH: 0x30 <len> <topic_len> <topic> <message>

PostgreSQL:文档明确建议生产环境使用 Hyperdrive,因为裸的 Postgres 协议(启动、认证、查询消息)非常复杂。

5.4 错误处理模式

带指数退避的重试:

async function connectWithRetry(addr: SocketAddress, opts: SocketOptions, maxRetries = 3): Promise<Socket> { for (let i = 1; i <= maxRetries; i++) { try { const socket = connect(addr, opts); await socket.opened; return socket; } catch (error) { if (i === maxRetries) throw error; await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i - 1))); // 指数退避 } } throw new Error('Unreachable'); }

超时控制(Promise.race):

async function connectWithTimeout(addr: SocketAddress, opts: SocketOptions, ms = 5000): Promise<Socket> { const socket = connect(addr, opts); const timeout = new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms)); await Promise.race([socket.opened, timeout]); return socket; }

主备降级:

async function connectWithFallback(primary: string, fallback: string, port: number): Promise<Socket> { try { const socket = connect({ hostname: primary, port }, { secureTransport: "on" }); await socket.opened; return socket; } catch { return connect({ hostname: fallback, port }, { secureTransport: "on" }); } }

5.5 安全模式

目标地址白名单(防 SSRF)——TCP Sockets 可以直接访问内网,一旦目标地址由用户输入控制就会形成 SSRF 漏洞,必须严格校验:

const ALLOWED_HOSTS = ['db.internal.company.net', 'api.internal.company.net', /^10\.0\.1\.\d+$/]; function isAllowed(hostname: string): boolean { return ALLOWED_HOSTS.some(p => p instanceof RegExp ? p.test(hostname) : p === hostname); } export default { async fetch(req: Request): Promise<Response> { const target = new URL(req.url).searchParams.get('host'); if (!target || !isAllowed(target)) return new Response('Forbidden', { status: 403 }); const socket = connect({ hostname: target, port: 443 }); // Use socket... } };

连接池——每个请求最多 6 个并发 socket(硬限制),复用连接可显著降低建连开销:

class SocketPool { private pool = new Map<string, Socket[]>(); async acquire(hostname: string, port: number): Promise<Socket> { const key = `${hostname}:${port}`; const sockets = this.pool.get(key) || []; if (sockets.length > 0) return sockets.pop()!; const socket = connect({ hostname, port }, { secureTransport: "on" }); await socket.opened; return socket; } release(hostname: string, port: number, socket: Socket): void { const key = `${hostname}:${port}`; const sockets = this.pool.get(key) || []; if (sockets.length < 3) { sockets.push(socket); this.pool.set(key, sockets); } else socket.close(); } }

5.6 多协议网关

把协议探测逻辑注册成一张表,用 URL 路由分发,即可构建"一个 Worker 探测多种服务"的网关:

interface Protocol { name: string; defaultPort: number; test(host: string, port: number): Promise<string>; } const PROTOCOLS: Record<string, Protocol> = { redis: { name: 'redis', defaultPort: 6379, async test(host, port) { const socket = connect({ hostname: host, port }); try { const writer = socket.writable.getWriter(); await writer.write(new TextEncoder().encode('*1\r\n$4\r\nPING\r\n')); writer.releaseLock(); const reader = socket.readable.getReader(); const { value } = await reader.read(); return new TextDecoder().decode(value || new Uint8Array()); } finally { await socket.close(); } } } }; export default { async fetch(req: Request): Promise<Response> { const url = new URL(req.url); const proto = url.pathname.slice(1); // /redis const host = url.searchParams.get('host'); if (!host || !PROTOCOLS[proto]) return new Response('Invalid', { status: 400 }); const result = await PROTOCOLS[proto].test(host, parseInt(url.searchParams.get('port') || '') || PROTOCOLS[proto].defaultPort); return new Response(result); } };

六、配置与部署:Wrangler、Tunnel 与周边服务

configuration.md 覆盖了从最小配置到生产集成的完整链路。

6.1 基础 Wrangler 配置

TCP Sockets 在 Workers 运行时中默认可用,无需任何特殊配置:

{ "name": "private-network-worker", "main": "src/index.ts", "compatibility_date": "2025-01-01" }

6.2 环境变量

把连接细节放进vars,Worker 中通过env读取:

{ "vars": { "DB_HOST": "10.0.1.50", "DB_PORT": "5432" } }
interface Env { DB_HOST: string; DB_PORT: string; } export default { async fetch(req: Request, env: Env): Promise<Response> { const socket = connect({ hostname: env.DB_HOST, port: parseInt(env.DB_PORT) }); } };

多环境隔离:用env块区分环境,部署时用--env指定:

{ "vars": { "DB_HOST": "localhost" }, "env": { "staging": { "vars": { "DB_HOST": "staging-db.internal.net" } }, "production": { "vars": { "DB_HOST": "prod-db.internal.net" } } } }
wrangler deploy --env staging wrangler deploy --env production

6.3 与 Cloudflare Tunnel 集成(连接私有网络的核心链路)

Worker 本身无法直接访问你的私有网络,标准做法是把 TCP Socket 指向 Tunnel 主机名,由 cloudflared 转发到内网:

Worker (TCP Socket) → Tunnel hostname → cloudflared → Private Network

快速搭建步骤:

  1. 在私有网络内的服务器上安装 cloudflared
  2. 创建隧道:cloudflared tunnel create my-private-network
  3. 在config.yml中配置路由:
tunnel: <TUNNEL_ID> credentials-file: /path/to/<TUNNEL_ID>.json ingress: - hostname: db.internal.example.com service: tcp://10.0.1.50:5432 - service: http_status:404 # 必需的 catch-all 兜底规则
  1. 运行隧道:cloudflared tunnel run my-private-network
  2. 从 Worker 连接:
const socket = connect( { hostname: "db.internal.example.com", port: 5432 }, // Tunnel 主机名 { secureTransport: "on" } );

Tunnel 的详细配置见 tunnel/configuration.md。

6.4 Smart Placement:自动靠近后端

Smart Placement 会根据观测到的连接延迟自动把 Worker 调度到离 TCP 目标更近的位置,降低延迟:

{ "placement": { "mode": "smart" } }

配置选项详见 smart-placement 参考。

6.5 Secrets 管理

敏感凭据(如数据库密码)必须用wrangler secret存储,而非写进wrangler.jsonc:

wrangler secret put DB_PASSWORD # 按提示输入值

Worker 中通过env.DB_PASSWORD读取,用于协议握手或认证。

6.6 本地开发

用wrangler dev测试。注意:本地模式可能无法访问私有网络,开发时应使用公共端点或 mock 服务器:

const config = process.env.NODE_ENV === 'dev' ? { hostname: 'localhost', port: 5432 } // Mock : { hostname: 'db.internal.example.com', port: 5432 }; // 生产

6.7 连接串解析模式

从标准连接串中提取 host 与 port:

function parseConnectionString(connStr: string): SocketAddress { const url = new URL(connStr); // e.g., "postgres://10.0.1.50:5432/mydb" return { hostname: url.hostname, port: parseInt(url.port) || 5432 }; }

6.8 Hyperdrive:数据库场景的首选

对 PostgreSQL/MySQL,优先使用 Hyperdrive 而非裸 TCP(自带连接池与缓存):

{ "hyperdrive": [{ "binding": "DB", "id": "<HYPERDRIVE_ID>" }] }

完整配置见 Hyperdrive 参考。

6.9 兼容性

TCP Sockets 在所有现代 Workers 运行时中可用,使用当前日期作为compatibility_date(如"2025-01-01")即可,无需特殊 flag。

七、坑与排查:限制、常见错误与性能优化

gotchas.md 汇总了最容易踩的坑。

7.1 平台硬限制

限制数值
每请求最大并发 socket 数6(硬限制)
socket 生命周期请求时长
连接超时平台决定,不可配置

问题:并发超过 6 个会直接抛错。解决:按 6 个一批处理:

for (let i = 0; i < hosts.length; i += 6) { const batch = hosts.slice(i, i + 6).map(h => connect({ hostname: h, port: 443 })); await Promise.all(batch.map(async s => { /* use */ await s.close(); })); }

被封锁的目标:Cloudflare 自身 IP(如1.1.1.1)、localhost(127.0.0.1)、端口 25(SMTP)、Worker 自身 URL 均被封锁。解决方案是改用公网 IP 或 Tunnel 主机名。

作用域要求:在全局作用域创建的 socket 会失败——socket 与请求生命周期绑定,必须在 handler 内创建:

export default { async fetch() { const socket = connect(...); } }

7.2 常见错误速查

错误信息原因解决方案
proxy request failed目标被封锁(Cloudflare IP/localhost/端口 25)、DNS 失败、网络不可达校验目标、用 Tunnel 主机名、try/catch 捕获
TCP Loop detectedWorker 连接到了自己连接外部服务,而非 Worker 自身主机名
Port 25 prohibitedSMTP 端口被封锁发邮件改用 Email Workers API
socket is not open关闭后继续读写始终用 try/finally 保证关闭顺序
连接超时无内置超时用Promise.race()自行实现(见 5.4)

7.3 TLS/SSL 问题

StartTLS 时机:过早调用startTls()会失败。正确顺序是:发送协议特定的 STARTTLS 命令 → 等待服务端返回 OK → 再调用socket.startTls()。

证书校验:自签名证书会导致握手失败。应使用正规证书,或通过 Tunnel(由 Tunnel 负责 TLS 终结)。

7.4 性能问题

问题解决方案
未使用连接池,每请求新建连接数据库场景用 Hyperdrive(内置池化);其他场景自建池(见 5.5)
未启用 Smart Placement,后端延迟高在wrangler.jsonc中启用{ "placement": { "mode": "smart" } }
忘记关闭 socket,资源泄漏始终用 try/finally

7.5 数据处理问题

  • 假设一次读取拿到全部数据:错误。必须循环reader.read()直到done === true(见 5.1)。
  • 文本编码错误:按需指定编码,如new TextDecoder('iso-8859-1').decode(data)。

7.6 安全与选型

SSRF 漏洞:目标地址由用户控制时可直接访问内部服务。解决:用严格白名单校验(见 5.5)。

何时改用其他方案:

场景替代方案理由
PostgreSQL/MySQLHyperdrive连接池、缓存
HTTP/HTTPSfetch()更简单、内置
带 SSRF 防护的 HTTPVPC Services(2025+ beta)声明式绑定

7.7 调试技巧

  1. 打印连接信息:const info = await socket.opened; console.log(info.remoteAddress);
  2. 先用公共服务测试:tcpbin.com:4242 的 echo 服务
  3. 验证 Tunnel:cloudflared tunnel info <name>与cloudflared tunnel route ip list

八、快速参考

任务代码
导入import { connect } from 'cloudflare:sockets';
连接connect({ hostname: "host", port: 443 })
带 TLSconnect(addr, { secureTransport: "on" })
StartTLS握手后调用socket.startTls()
写入await writer.write(data); await writer.close();
读取const { value } = await reader.read();
错误处理try { await socket.opened; } catch { }
总是关闭try { } finally { await socket.close(); }

九、阅读路径与相关资源

本主题在仓库中位于 cloudflare-deploy 技能 的 Networking 分类下(references/workers-vpc/),建议按以下顺序阅读:

  1. workers-vpc/README.md — 概览与选型决策
  2. workers-vpc/api.md — Socket 接口、类型与方法(本文骨架)
  3. workers-vpc/configuration.md — Wrangler 配置与 Tunnel 集成
  4. workers-vpc/patterns.md — 数据库、协议与错误处理实战
  5. workers-vpc/gotchas.md — 限制、封锁端口与常见错误

关联技术文档:Tunnel 配置、Smart Placement 配置、Hyperdrive 配置。围绕这些参考文档的部署流程、认证与决策树,可回到 cloudflare-deploy/SKILL.md 查阅。

要点回顾:始终在finally中关闭 socket;用白名单防 SSRF;数据库场景优先 Hyperdrive;HTTP 场景优先fetch();配合 Smart Placement 降低到私有网络的延迟;牢记每请求 6 个并发 socket 的硬限制。

【免费下载链接】autoskills

One command. Your entire AI skill stack. Installed.

项目地址:https://gitcode.com/gh_mirrors/au/autoskills
点击查看免费下载

相关推荐

上一篇:Beyond Compare 5一键激活实用指南:从密钥生成到完美授权的完整解决方案
下一篇:Beyond Compare 5密钥生成器:从评估限制到完整激活的实用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询