【免费下载链接】autoskills
One command. Your entire AI skill stack. Installed.
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 私有 API | VPC Services(beta,另见文档) | SSRF 安全、声明式绑定 |
| PostgreSQL/MySQL 数据库 | Hyperdrive | 内置连接池与缓存 |
| 自定义 TCP 协议(SSH、MQTT、私有协议) | TCP Sockets(本文主题) | 完全掌控线上协议 |
| 追求最低延迟的简单 HTTP | TCP 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 ): Socket2.1SocketAddress:目标地址
interface SocketAddress { hostname: string; // DNS 主机名或 IP 地址 port: number; // TCP 端口(1-65535,排除被封锁的端口) }| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
hostname | string | 目标主机名或 IP | "db.internal.net"、"10.0.1.50" |
port | number | TCP 端口号 | 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 模式 |
allowHalfOpen | boolean | false | 是否允许半关闭连接 |
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 production6.3 与 Cloudflare Tunnel 集成(连接私有网络的核心链路)
Worker 本身无法直接访问你的私有网络,标准做法是把 TCP Socket 指向 Tunnel 主机名,由 cloudflared 转发到内网:
Worker (TCP Socket) → Tunnel hostname → cloudflared → Private Network快速搭建步骤:
- 在私有网络内的服务器上安装 cloudflared
- 创建隧道:
cloudflared tunnel create my-private-network - 在
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 兜底规则- 运行隧道:
cloudflared tunnel run my-private-network - 从 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 detected | Worker 连接到了自己 | 连接外部服务,而非 Worker 自身主机名 |
Port 25 prohibited | SMTP 端口被封锁 | 发邮件改用 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/MySQL | Hyperdrive | 连接池、缓存 |
| HTTP/HTTPS | fetch() | 更简单、内置 |
| 带 SSRF 防护的 HTTP | VPC Services(2025+ beta) | 声明式绑定 |
7.7 调试技巧
- 打印连接信息:
const info = await socket.opened; console.log(info.remoteAddress); - 先用公共服务测试:tcpbin.com:4242 的 echo 服务
- 验证 Tunnel:
cloudflared tunnel info <name>与cloudflared tunnel route ip list
八、快速参考
| 任务 | 代码 |
|---|---|
| 导入 | import { connect } from 'cloudflare:sockets'; |
| 连接 | connect({ hostname: "host", port: 443 }) |
| 带 TLS | connect(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/),建议按以下顺序阅读:
- workers-vpc/README.md — 概览与选型决策
- workers-vpc/api.md — Socket 接口、类型与方法(本文骨架)
- workers-vpc/configuration.md — Wrangler 配置与 Tunnel 集成
- workers-vpc/patterns.md — 数据库、协议与错误处理实战
- 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.
相关推荐
Cloudflare Workers TCP Sockets 配置指南:用 Wrangler、Tunnel 与 Smart Placement 打通私有网络
Cloudflare Workers TCP Sockets 配置指南:用 Wrangler、Tunnel 与 Smart Placement 打通私有网络 导
人工智能AI 技能AI 插件YOLO-World注意力权重分析:检测精度与注意力分布相关性研究
YOLO World注意力权重分析:检测精度与注意力分布相关性研究 引言:注意力机制在目标检测中的关键作用 在计算机视觉领域,目标检测算法的性能提升很大程度上依
网络通信WinFsp Launch API (launch.h) 详解:通过 WinFsp 启动器管理用户态进程实例
WinFsp Launch API launch.h 详解:通过 WinFsp 启动器管理用户态进程实例 WinFsp 除了提供文件系统挂载能力外,还内置了一个
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考