Cloudflare Workers 私有网络 TCP 连接实战:cloudflare:sockets 常用模式完全指南 Cloudflare Workers 私有网络 TCP 连接实战cloudflare:sockets 常用模式完全指南【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills本文基于 Cloudflare Workers VPC 连接能力的核心文档系统讲解如何在 Worker 中通过cloudflare:sockets模块建立出站 TCP 连接覆盖基础读写、流式响应、Redis/MQTT 等自定义协议对接、重试与超时等错误处理、防 SSRF 白名单与连接池等安全模式以及多协议网关的完整实现。读完本文你将能够把 Worker 当作一个拥有完整 TCP 协议控制能力的客户端安全、稳定地接入 AWS、Azure、GCP、本地数据中心等私有网络内的数据库、消息中间件与自定义服务。前置知识TCP Sockets API 核心回顾在进入模式之前先回顾cloudflare:sockets的核心接口详见 api.md。所有模式都建立在同一个入口函数之上import { connect } from cloudflare:sockets; function connect(address: SocketAddress, options?: SocketOptions): Socket地址与选项SocketAddress由目标主机与端口构成DNS 名称在连接时解析支持 IPv4、IPv6 以及10.x、172.16.x、192.168.x等私有 IPinterface SocketAddress { hostname: string; // DNS 主机名或 IP 地址如 db.internal.net、10.0.1.50 port: number; // TCP 端口1-65535排除被阻止端口 }SocketOptions控制 TLS 模式与半开连接行为interface SocketOptions { secureTransport?: off | on | starttls; allowHalfOpen?: boolean; }字段类型默认值说明secureTransportoff \| on \| starttlsoffoff为明文 TCP适合测试与可信内网on立即执行 TLS 握手适合 HTTPS、加密数据库、SSHstarttls先明文通信、随后用startTls()升级适合 PostgreSQL、SMTP、IMAPallowHalfOpenbooleanfalsefalse时关闭读流会自动关闭写流true时两个方向独立关闭Socket 接口interface Socket { readable: ReadableStreamUint8Array; // 读取数据 writable: WritableStreamUint8Array; // 写入数据 opened: PromiseSocketInfo; // 连接成功时 resolve失败时 reject closed: Promisevoid; // 双向完全关闭后 resolve close(): Promisevoid; // 优雅关闭等待未完成写入 startTls(): Socket; // 升级为 TLS仅 secureTransport 为 starttls 时可用 }其中SocketInfo携带remoteAddress与localAddress字段可能为undefined可用于连接日志与调试。使用时的黄金法则始终在try/finally中调用await socket.close()这一规则贯穿本文所有模式。基础模式简单请求-响应最常见的模式建立连接、写入请求、读取响应、关闭。以 echo 服务为例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\n)); await writer.close(); const reader socket.readable.getReader(); const { value } await reader.read(); return new Response(value); } finally { await socket.close(); }这里await socket.opened是连接能否成功的第一道校验连接失败会在此处 reject应配合 try/catch 捕获见下文错误处理。读取全部数据TCP 是字节流协议一次reader.read()返回的只是当前到达的一个分块并不保证包含完整响应。要可靠地读完全部数据必须循环读取直到done true再把各分块拼接为连续的Uint8Arrayasync function readAll(socket: Socket): PromiseUint8Array { 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; }gotchas.mdgotchas.md中明确将假设单次读取即获得全部数据列为常见数据处理的坑本文所有协议示例默认采用此循环读取方式。流式响应当目标服务持续产出数据日志流、事件流时不必在 Worker 内聚合可以直接把 socket 的readable作为Response的 body 透传出去实现端到端流式传输// Stream socket data directly to HTTP response 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);这一模式充分利用了 Web 标准的ReadableStream兼容性socket 的readable与 HTTP 响应天然同构无需额外缓冲。协议示例TCP Sockets 的价值在于对线上协议wire protocol的完全控制。这里给出三种典型协议的客户端实现骨架。Redis RESPRedis 使用 RESPREdis Serialization Protocol协议。发送一个GET命令需构造*2\r\n$3\r\nGET\r\n$keylen\r\nkey\r\n格式接收端则返回$len\r\ndata\r\n字符串或$-1\r\nnull// Send: *2\r\n$3\r\nGET\r\n$keylen\r\nkey\r\n // Recv: $len\r\ndata\r\n or $-1\r\n for 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));写入后按 RESP 规则解析响应先按\r\n切分读取行再根据类型前缀、-、:、$、*决定后续需要读取的字节数配合上文的readAll与循环读取完成解析。需要批量操作时应实现命令流水线减少往返。PostgreSQL生产环境强烈建议改用 Hyperdrive。原生的 PostgreSQL 线协议非常复杂包含启动握手startup、认证auth与查询消息query messages等多个阶段自行用裸 TCP 实现意味着要把整套协议状态机都搬到 Worker 里而 Hyperdrive 已经内置了连接池、缓存与协议优化。只有在需要控制协议细节如自定义认证流程时才考虑裸协议此时需从 PostgreSQL 文档实现 startup 消息与 SASL 认证工作量大且易错。MQTTMQTT 是物联网场景常见的轻量消息协议固定包头结构清晰适合手工构造。以下代码构造 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其中0x10为 CONNECT 报文类型len为剩余长度剩余长度编码是变长的大于 127 时需拆成多个字节0x00 0x04 MQTT为协议名0x04为协议级别MQTT 3.1.1随后是连接标志位含 Clean Session、Will、用户名密码位与 keepalive 字段。连接建立后可继续发送0x30开头的 PUBLISH 报文、0x82的 SUBSCRIBE 报文并按 QoS 处理 PUBACK/PUBREC/PUBREL/PUBCOMP 回执。错误处理模式重试与指数退避网络抖动、瞬时不可达在私有网络环境中并不罕见。connectWithRetry在socket.opened失败时按指数退避重试最多maxRetries次async function connectWithRetry(addr: SocketAddress, opts: SocketOptions, maxRetries 3): PromiseSocket { 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))); // Exponential backoff } } throw new Error(Unreachable); }注意退避间隔分别为 1s、2s、4s1000 * 2^(i-1)幂等操作如只读查询才适合整体重试非幂等写入应谨慎避免重复副作用。超时cloudflare:sockets没有内置连接超时设置gotchas.md 明确指出连接超时由平台决定、不可配置。因此需要手动用Promise.race实现超时async function connectWithTimeout(addr: SocketAddress, opts: SocketOptions, ms 5000): PromiseSocket { const socket connect(addr, opts); const timeout new Promisenever((_, reject) setTimeout(() reject(new Error(Timeout)), ms)); await Promise.race([socket.opened, timeout]); return socket; }超时触发后若socket.opened后续仍然 resolve需要确保对 socket 做兜底close()防止资源泄漏。主备回退面向高可用架构当主节点不可达时自动切换备用节点async function connectWithFallback(primary: string, fallback: string, port: number): PromiseSocket { try { const socket connect({ hostname: primary, port }, { secureTransport: on }); await socket.opened; return socket; } catch { return connect({ hostname: fallback, port }, { secureTransport: on }); } }更完整的回退方案可结合connectWithRetry与connectWithTimeout先对主节点做带超时重试再降级到备用节点并把降级事件记入日志便于观测。安全模式目标白名单防 SSRFWorker 的fetch入口天然接收外部请求如果让请求者自由指定 TCP 连接目标攻击者就能借 Worker 扫描内网。必须对用户可控的目标做严格校验。白名单支持精确主机名与正则表达式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): PromiseResponse { 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... } };gotchas.md中的 SSRF 章节给出了同思路的最小实现ALLOWED数组加includes判断。生产建议同时校验端口并对正则形式的网段如10.0.1.0/24做规范化处理防止127.0.0.1、Cloudflare IP、端口 25 等被阻止目标借白名单绕过。连接池Worker 的请求级生命周期决定了每次请求新建 TCP 连接都会带来握手开销。SocketPool按hostname:port维度缓存空闲连接超过容量上限示例为 3 条即关闭多余连接class SocketPool { private pool new Mapstring, Socket[](); async acquire(hostname: string, port: number): PromiseSocket { 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(); } }需要留意两个约束其一每个请求最多 6 个并发 socket硬限制连接池若在模块级缓存连接需确保不会在一个请求内超过该上限其二gotchas.md指出 socket 与请求生命周期绑定跨请求复用连接在不同运行时语义下可能有差异建议以 Worker 实例级缓存 请求内限流的方式使用。对于数据库场景更稳妥的选择是直接使用 Hyperdrive自带连接池。多协议网关把以上模式组装起来可以构建一个协议探测网关通过 URL 路径选择协议/redis、/mqtt等对指定主机发送协议级探测命令并返回结果。这是 Worker 连私有网络场景的典型复合应用interface Protocol { name: string; defaultPort: number; test(host: string, port: number): Promisestring; } const PROTOCOLS: Recordstring, 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): PromiseResponse { 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); } };两个值得注意的实现细节writer.releaseLock()允许在写完请求后立即创建 reader同一 socket 的读写流共用底层的锁管理需要释放 writer 锁端口参数使用parseInt(...) || defaultPort的防御式解析非法输入自动回退默认端口。向此网关新增协议只需在PROTOCOLS注册表追加一个实现天然可扩展。运行时限制与常见排错所有模式都必须遵守平台硬限制完整清单见 gotchas.md 与 README.md限制说明每个请求最大并发 socket6硬限制超出即报错需分批处理被阻止的目标Cloudflare 自身 IP如 1.1.1.1、localhost127.0.0.1、端口 25SMTP、Worker 自身 URL创建位置必须在 handlerfetch回调内创建全局作用域创建会失败socket 生命周期与请求时长绑定无内置连接超时配置分批连接示例应对 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(); })); }常见报错与处置速查proxy request failed多为目标被阻止Cloudflare IP / localhost / 端口 25、DNS 解析失败或网络不可达。应校验目标、改用 Tunnel 主机名并用 try/catch 捕获。TCP Loop detectedWorker 连接到了自身应改为连接外部服务。Port 25 prohibitedSMTP 端口被阻止发邮件请改用 Email Workers API。socket is not open关闭后仍读写必须用try/finally保证关闭顺序。连接超时无内置超时用Promise.race([socket.opened, timeout])自行实现。StartTLS 时序错误startTls()过早调用会失败必须先发送协议特定的 STARTTLS 命令、等待服务端 OK再调用socket.startTls()且之后必须使用其返回的新 socket。自签名证书失败使用正规证书或让 Tunnel 处理 TLS 终止。与 Tunnel、Smart Placement、Hyperdrive 的组合TCP Sockets 的实战价值通常不是单独使用而是与私有网络接入链路组合。标准架构为Worker (TCP Socket) → Tunnel 主机名 → cloudflared → 私有网络在私有网络内服务器上安装cloudflared并创建隧道后通过config.yml的 ingress 规则把隧道主机名路由到内网 TCP 服务详细配置见 Tunnel 配置参考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 # Required catch-all随后 Worker 即可直连隧道主机名并启用 TLSconst socket connect( { hostname: db.internal.example.com, port: 5432 }, // Tunnel hostname { secureTransport: on } );同时可组合两类优化配置细节见 configuration.mdSmart Placement在wrangler.jsonc中配置{ placement: { mode: smart } }平台会观察连接延迟自动把 Worker 调度到靠近 TCP 目标的位置显著降低内网往返延迟。Hyperdrive{ hyperdrive: [{ binding: DB, id: HYPERDRIVE_ID }] }PostgreSQL/MySQL 场景用 Hyperdrive 代替裸 socket获得连接池与缓存能力连接串解析可用new URL(postgres://10.0.1.50:5432/mydb)提取 host 与 port。连接信息建议通过wrangler.jsonc的vars与环境化配置管理wrangler deploy --env staging敏感凭据用wrangler secret put DB_PASSWORD注入避免写入配置文件。结语模式选型清单最后给出从本文提炼的可执行清单覆盖大多数私有网络接入场景能用fetch()解决就不要用裸 TCPHTTP/HTTPS 场景优先fetch()需要 SSRF 防护与声明式绑定时关注 VPC Servicesbeta。数据库优先 HyperdrivePostgreSQL/MySQL 自带连接池与缓存远超手写协议。必须裸协议时先画清协议报文格式如 RESP、MQTT 固定头用readAll循环读取绝不做单次读取假设。错误处理三板斧Promise.race加超时、指数退避重试、主备回退。安全红线用户可控目标必须过白名单精确主机名 正则禁止直连被阻止目标。资源纪律try/finally必关 socket连接池容量控制在 6 并发上限内分批处理批量连接。组合优化Tunnel 打通内网、Smart Placement 降低延迟、wrangler secret管理凭据。按此清单落地你的 Worker 就能成为一张安全、稳健、低延迟的私有网络访问网无论是 Redis、MQTT、SSH 还是任意自定义二进制协议都尽在掌握。【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考