Puter Socket API 完全指南:在浏览器里创建原始 TCP Socket

发布时间:2026/9/9 20:44:16
Puter Socket API 完全指南:在浏览器里创建原始 TCP Socket Puter Socket API 完全指南在浏览器里创建原始 TCP Socket【免费下载链接】puter The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puter导读Puter 是一个免费、开源且可自行部署的“Internet Computer”云操作系统其官方前端 SDKputer-js中内置了puter.net网络命名空间。其中的 Socket API 让你能够在浏览器环境中直接创建原始 TCP Socket绕过浏览器原生网络栈的限制与任意主机建立面向字节流的连接。本文以 Networking/Socket.md 为主体结合 PSocket.js 与相关测试源码系统讲解 Socket API 的构造方式、方法与事件语义、底层 Wisp 隧道原理以及真实可运行的代码示例。读完本文你将掌握如何让 Web 应用直接进行 TCP 级别的通信而不是只能发 HTTP 请求、如何正确消费 open/data/close/error 四类事件以及浏览器内裸 TCP这一能力背后的实现机制与边界条件。该 API 面向的可用平台来自文档 frontmatter为websites网站与 apps应用即任何引入puter-js的前端页面均可使用。什么是puter.net.Socket文档对 Socket API 给出了一句话定义The Socket API lets you create a raw TCP socket which can be used directly in the browser.Socket API 允许你创建一个可直接在浏览器中使用的原始 TCP socket。普通的浏览器 JavaScript 只能通过XMLHttpRequest/fetch/WebSocket通信无法建立裸 TCP 连接也无法读取任意二进制字节流。Puter 通过一个Wisp 中继relay隧道解决了这一问题浏览器端先与 Wisp 服务器建立 WebSocket再由服务器代为发起真正的 TCP 连接从而把服务器端的能力透传给前端。这一点在源码中有明确注释A raw TCP socket in the browser, tunnelled over the Wisp relay. Construct it withputer.net.Socket(hostname, port); the connection is established asynchronously, so write onceopenhas fired. src/puter-js/src/modules/networking/PSocket.js#L30-L36也就是说使用方式上它看起来与 Node.js 的net.Socket几乎一致但底层承载是浏览器 WebSocket Wisp 协议。在puter-js的初始化逻辑中puter.net命名空间在 src/puter-js/src/index.js#L773-L803 处注册共包含四个成员成员说明Socket原始 TCP Socket 类即PSockettls.TLSSocketTLS 加密的 TCP Socket 类fetch基于上述 Socket 实现的、不受 CORS 限制的fetchgenerateWispV1URL生成中继服务器 一次性令牌的 Wisp v1 连接 URL其中fetch与TLSSocket分别有独立文档见 fetch.md 与 TLSSocket.md本文聚焦原始Socket。构造函数与参数语法const socket new puter.net.Socket(hostname, port);hostnameString必填要连接服务器的主机名可以是 IP 地址也可以是域名例如example.com、192.168.1.10。它会被原样交给底层 Wisp 中继用于发起 TCP 连接Wisp CONNECT 包中携带的即该字符串见 parsers.js#L45-L49。portNumber必填服务器上的端口号。构造时会写入 Wisp CONNECT 包的 16 位端口字段见 parsers.js#L125-L126因此有效范围遵循 TCP/UDP 的 0–65535实际业务端口通常为 1–65535。构造器内部签名与实现位于 PSocket.js#L44-L108。返回与连接建立时机构造函数返回一个Socket对象内部类名为PSocket继承自统一的EventListener基类。注意TCP 连接是异步建立的——调用构造函数并不会立刻完成拨号。源码注释明确提醒write onceopenhas fired等open事件触发后再写数据。任何未经open就调用write()的行为都属于未定义时序应避免。一个值得注意的细节是PSocket内部以异步 IIFE 完成全部握手流程而外层没有任何await因此任何连接失败都必须以error/close事件的形式通知调用方而不会产生未捕获的 Promise rejection。源码在 PSocket.js#L102-L107 专门处理了这一情况})().catch((e) { // Nothing awaits this body, so a failure to connect has to reach // the caller as an error event rather than an unhandled rejection. this.emit(error, e instanceof Error ? e : new Error(String(e))); this.emit(close, true); });首次使用的自动握手在首次构造 Socket即模块级单例wispInfo.handler尚未初始化时库会依次执行鉴权在puter.env web且没有puter.authToken时自动调用puter.ui.authenticateWithPuter()拉起认证模块常量requireAuth true申请中继令牌向${puter.APIOrigin}/wisp/relay-token/create发送 POST 请求换取{ token, server }建立 Wisp WebSocket用返回的服务器地址实例化PWispHandler等待 WebSocket 真正打开打开失败会丢弃该 handler下次构造自动重拨注册 TCP 流调用wispInfo.handler.register(host, port, callbacks)拿到_streamID随后在下一个宏任务中发出open事件。以上流程对应 PSocket.js#L47-L100。也就是说同一个页面里的多个 Socket 会共享同一个 Wisp 中继连接而不是每个 Socket 单独建一条 WebSocket——wispInfo.handler是模块级单例PSocket.js#L8-L11。方法详解socket.write(data)向 Socket 写入数据对应源码 PSocket.js#L129-L142。参数data支持三种类型类型处理方式string用TextEncoder编码为 UTF-8 字节后发送Uint8Array及各类 TypedArray直接发送其底层字节ArrayBuffer包装为Uint8Array后发送数据校验传入以上三种之外的任何值例如number、普通对象都会抛出异常异常消息为Invalid data type (not TypedArray, ArrayBuffer or String!!)对应 PSocket.js#L140。这一点也由单元测试覆盖见下文测试验证一节。此外源码中的签名还允许第二个可选参数callback——数据被交给中继后触发源码注释为invokingcallbackonce it has been handed to the relay文档中未作要求可作为进阶用法。socket.write(GET / HTTP/1.1\r\nHost: example.com\r\n\r\n); // 发送字符串 socket.write(new TextEncoder().encode(raw bytes)); // 发送二进制socket.close()主动关闭 TCP Socket对应 PSocket.js#L148-L150。它向 Wisp 中继发送该流的 CLOSE 包随后远端会收到关闭通知。发起关闭后本地会触发close事件hadError为false。socket.addListener(event, handler)监听 Socket 事件的一种等价格式源码 PSocket.js#L116-L118 中直接转发给this.on(...args)addListener (...args) { this.on(...args); }参数说明eventSocketEvent要监听的事件名合法值为open、data、close、errorhandlerFunction事件发生时要调用的回调回调参数随事件类型而异见下文 Events 一节。注册的事件名必须是上述集合之一PSocket继承自EventListener构造时以[data, drain, open, error, close, tlsdata, tlsopen, tlsclose]声明了允许注册的事件PSocket.js#L45。注册未知事件会静默返回undefined而不会抛错——集成测试net.suite.ts专门验证了这一点socket.on(net-suite-unknown, handler)返回undefined。事件详解EventsSocket 是典型的事件驱动接口对外文档定义四类事件。以下表格汇总各事件的触发时机与回调签名对应文档 Events 一节。事件触发时机回调参数openSocket 初始化完成、可开始发送数据无data远端服务器通过该 TCP Socket 发来数据buffer: Uint8Array收到的数据字节closeSocket 关闭hadError: boolean是否因错误而关闭errorSocket 遇到错误随后不久会触发closeerror: Error人类可读原因在error.messagesocket.on(open, callback)当 Socket 已初始化并可以发送数据时触发。所有write()都应放在此回调内。在源码实现中open事件是在 TCP 流注册成功后通过setTimeout(..., 0)发出的PSocket.js#L98-L100因此它始终是异步派发的。socket.on(data, callback)远端服务器向本 Socket 发送数据时触发。回调收到的buffer为Uint8Array即裸二进制数据——文档明确其类型并非string需要开发者自行解码例如用TextDecoder或按二进制协议解析。这也是原始 TCP的核心意义拿到的是未被 HTTP/WebSocket 框架加工过的字节流。socket.on(close, callback)Socket 关闭时触发。回调参数hadError为booleantrue表示 Socket 是因为错误而关闭的。关闭原因映射底层 Wisp 协议在收到对端 CLOSE 包时会携带一个 8 位关闭原因码。在 PSocket.js#L86-L95 中closeCallBack: (reason) { if ( reason ! 0x02 ) { this.emit(error, new Error(errors[reason])); this.emit(close, true); return; } this.emit(close, false); },即只有关闭原因码0x02正常关闭才会得到hadError false其它一切原因码都会先触发一次error事件随后以hadError true触发close。当因错误关闭时error.message会给出人类可读的原因文本这些文本来自 parsers.js#L17-L27 中的错误码表原因码含义error.message内容0x01Reason unspecified or unknown.0x03Unexpected stream closure due to a network error.网络错误导致意外关闭0x41Stream creation failed due to invalid information.目标为保留地址或端口非法0x42Stream creation failed due to an unreachable destination host.域名无法解析等0x43Stream creation timed out due to the destination server not responding.目标无响应拨号超时0x44Stream creation failed due to the destination server refusing the connection.连接被拒绝0x47TCP data transfer timed out.TCP 数据传输超时0x48Stream destination address/domain is intentionally blocked by the proxy server.被中继服务器主动屏蔽0x49Connection throttled by the server.被服务器限流提示由于被屏蔽地址0x48与限流0x49等语义存在实际应用中把拨号失败与域名被中继屏蔽区分对待是合理的错误消息中携带的原因码信息能帮你诊断问题。socket.on(error, callback)Socket 遇到错误时触发随后几乎立即触发close。回调参数error为Error对象人类可读的原因位于error.message。事件声明之外进阶源码级事件映射PSocketEventMapPSocket.js#L18-L28还声明了三个为 TLS 子类PTLSSocket准备的别名事件以及一个drain事件drain当写缓冲被清空flush后触发tlsdata/tlsopen/tlsclosePTLSSocket对data/open/close的拼写变体普通 Socket 用不到但 TLS 子类会把三者的行为对齐到无前缀事件名上详见 PTLS.js 与 TLSSocket.md。完整示例连接服务器并打印响应下面示例来自 Socket.mdhtml;net-basic可运行示例块演示了完整的生命周期open后写入一个 HTTP/1.1 请求data解码打印响应字节error打印原因close打印是否有错误html body script srchttps://js.puter.com/v2//script script const socket new puter.net.Socket(example.com, 80); socket.on(open, () { socket.write(GET / HTTP/1.1\r\nHost: example.com\r\n\r\n); }) const decoder new TextDecoder(); socket.on(data, (data) { puter.print(decoder.decode(data), { code: true }); }) socket.on(error, (error) { puter.print(Socket errored with the following reason: , error.message); }) socket.on(close, (hadError) { puter.print(Socket closed. Was there an error? , hadError); }) /script /body /html运行要点说明script srchttps://js.puter.com/v2//script引入puter-jsSDK使puter.net与puter.print可用请求行、请求头必须以\r\n结尾并在末尾额外加一个空行\r\n\r\n以终止请求头——这是 HTTP/1.1 文本协议的硬性要求因为此刻你在裸写协议而非依赖浏览器封装响应数据分多次到达是常态若需拼接完整响应体应在data回调中累积字节后再统一处理。补充示例二进制数据处理由于data事件给出的是Uint8Array你也可以按二进制协议处理数据而不是把一切当作文本。例如接收累计并转换为ArrayBufferconst socket new puter.net.Socket(127.0.0.1, 9000); let chunks []; socket.on(open, () { socket.write(new Uint8Array([0x00, 0x01, 0x02, 0xff])); // 发送原始字节 }); socket.on(data, (buffer) { chunks.push(buffer); // 按你的协议判断何时收到完整帧…… }); socket.on(error, (error) { console.error(socket error:, error.message); }); socket.on(close, (hadError) { console.log(closed, hadError , hadError); const full new Blob(chunks); // 或手动拼装 Uint8Array console.log(received, full.size, bytes); });主动关闭示例const socket new puter.net.Socket(example.com, 80); socket.on(open, () { socket.write(HEAD / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n); // 稍后主动断开 setTimeout(() socket.close(), 3000); }); socket.on(close, (hadError) { console.log(socket closed, hadError , hadError); // 主动关闭时为 false });底层原理Wisp 隧道与后端令牌机制要理解 Socket API 的边界需要知道它背后发生了三层握手。综合 PSocket.js、PWispHandler.js 与 parsers.js 可以看到完整链路前端鉴权浏览器端无authToken且运行环境为web时SDK 自动执行认证申请中继凭证POST${puter.APIOrigin}/wisp/relay-token/create获得中继服务器地址server与一次性令牌token。后端同时提供/wisp/relay-token/verify用于校验令牌有效性集成测试中可验证新鲜令牌可通过、伪造令牌被拒绝见 net.suite.ts#L36-L97。API 层也以puter.net.generateWispV1URL()暴露同样的能力返回形如${server}/${token}/的 Wisp v1 URLsrc/puter-js/src/index.js#L782-L797多路复用 TCP 流前端与中继建立 Wispv1/v2协议的 WebSocket此后每个puter.net.Socket只是该 WebSocket 上注册的一个streamregister(host, port, callbacks)返回_streamID。数据包遵循 Wisp 二进制帧格式——CONNECT(0x01)/DATA(0x02)/CONTINUE(0x03)/CLOSE(0x04)/INFO(0x05)每种包的解析与构造见 parsers.js#L40-L159。模块级单例wispInfo.handler初始server: wss://puter.cafe/源码标注 Unused currently意味着真正的中继地址由后端/wisp/relay-token/create接口动态下发前端只是在拿到地址后用new WebSocket(wispURL)建立连接PWispHandler.js#L16。一个重要的推论是Socket API 的功能边界取决于后端集群与 Wisp 中继的可用性属于前端自由调用、后端统一调度的架构。对自行部署 Puter 的用户而言这意味着中继服务是原始 Socket 能力的前置依赖。常见问题与注意事项1. 为什么要等open再writeTCP 连接是异步拨号的。构造后立即write()可能落在连接就绪之前open事件即stream 已注册成功、可发送的信号。2. 收到了error为什么还会跟着一个close这是刻意设计的事件顺序文档写明The close event is fired shortly after。底层非0x02的原因码会先派发error再以hadError true派发closePSocket.js#L86-L95。如果你的清理逻辑放在close里务必区分hadError。3. 写了不支持的数据类型会怎样write(42)、write({})这类调用会同步抛出异常而非异步报错Invalid data type (not TypedArray, ArrayBuffer or String!!)。4. 与浏览器原生fetch的差别通过 Socket 可以手动实现任意基于 TCP 的应用层协议项目还基于本 SocketHTTP 用Socket、HTTPS 用tls.TLSSocket实现了puter.net.fetch——一个不经过浏览器 HTTP 栈、因此不受 CORS 限制的流式 fetch实现见 requests.js。它要求 HTTP 文本协议的手工拼装、Content-Length与请求体一致不一致会拒绝请求详见 fetch.md。想要加密连接时应使用puter.net.tls.TLSSocket文档见 TLSSocket.md。5. Socket 关闭后还能继续读写吗不能。close()或远端关闭后应创建新的puter.net.Socket实例重新连接由于 Wisp handler 为单例复用新实例的握手成本较低。测试验证行为有据可查SDK 用两层测试锁定了上述行为可作为你排查问题时的参考单元测试PSocket.test.js以假 WebSocket 代替中继连接验证PSocket中继握手、同一 handler 复用多个 Socket 实例共享连接、以及write路径等内部行为API 集成测试net.suite.ts在真实后端上验证——relay-token/create能签发令牌、verify接受新令牌并拒绝伪造令牌无中继可拨号时Socket 以errorhadError true的close结束a socket with no relay configured reports an error and closes只接受声明过的事件名注册未知事件返回undefinedwrite非字符串/缓冲/类型化数组的数据会抛出固定错误消息TLS Sockettls.TLSSocket把data/open/close与tls前缀事件互为别名同一套事件处理代码可通用于两类 Socket。这些测试同时印证了一个可用性结论即使构造失败Socket 的失败也**永远通过事件而非异常**到达调用方因此编写健壮代码时始终要为error/close注册处理器避免静默失败。小结puter.net.Socket是 Puter 赋予 Web 应用的一扇底层网络窗口通过 Wisp 隧道 与后端 relay-token 机制 的配合让前端代码得以建立原始 TCP 连接、读写任意字节流。它的核心心智模型可以概括为四句话构造即异步拨号、open之后才可写、data给的是原始字节、error之后必有关闭。在此基础上你还可以进一步探索 TLSSocket加密连接与 puter.net.fetch基于 Socket 的无 CORS HTTP 客户端两块姊妹能力它们在 Puter 前端网络体系中构成从裸 TCP 到高层 HTTP 的完整梯度。【免费下载链接】puter The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询