尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

js-IPFS 消息端口协议详解:基于 ipfs-message-port-protocol 的跨线程 IPFS 编解码实战

js-IPFS 消息端口协议详解:基于 ipfs-message-port-protocol 的跨线程 IPFS 编解码实战 存储网络通信【免费下载链接】js-ipfsIPFS implementation in JavaScript项目地址https://gitcode.com/gh_mirrors/js/js-ipfs点击查看免费下载本篇技术指南围绕 packages/ipfs-message-port-protocol/README.md 展开深入讲解 js-IPFS 中通过MessagePort在浏览器多线程Worker/iframe之间搭建 IPFS 客户端与服务端通信所需的线协议编解码器wire protocol codecsCID、DAGNode、AsyncIterable 与 Callback。读完本文你将掌握这些类型为何无法被结构化克隆structured cloning直接传输、如何通过transfer列表零拷贝搬移底层内存以及如何在两端对称地进行编码与解码从而在自己的多线程 IPFS 应用中落地可复用的跨线程数据通道。背景为什么 IPFS 跨线程通信需要专门的协议层浏览器提供了 MessageChannel 与 MessagePort 作为线程间通信的底层通道postMessage默认使用结构化克隆算法structured cloning algorithm复制数据。然而 IPFS 核心数据类型并不总能被结构化克隆正确还原CIDContent Identifier依赖原型链上的asCID、/等属性与继承自multiformats/cid的方法克隆后只是一堆失去类身份的普通对象DAG 节点可能嵌套任意深度的对象、数组与 CID 实例AsyncIterable如ipfs.cat、ipfs.addAll的返回值本质上是运行时对象根本无法被克隆只能以「远程迭代器」的方式跨越线程逐个拉取数据项回调函数如ipfs.add的progress选项同样无法被克隆。ipfs-message-port-protocol包版本0.15.1见 package.json正是为解决这一系列问题而设计它为上述类型提供成对的encode*/decode*函数所有编码器都接受一个可选的transfer集合——若提供编码器会把值中所有Transferable字段加入其中从而让这些底层内存块在跨线程时被“转移”而非“复制”实现零拷贝。需要说明本包属于已被 Helia 取代的 js-IPFS 项目。仓库 README.md 及本包 README 开头均声明该项目已弃用deprecated、不再提供安全修复。本文面向仍在维护/迁移旧代码的开发者介绍其协议实现原理可作为理解 Helia 同类消息端口方案的参考。安装与模块入口npm 安装$ npm i ipfs-message-port-protocol包以 ESM 模块type: module发布要求 Node.js16.0.0、npm7.0.0。浏览器script标签通过 script 标签加载时包会将导出暴露为全局命名空间IpfsMessagePortProtocolscript srchttps://unpkg.com/ipfs-message-port-protocol/dist/index.min.js/script子路径导入subpath exports从 package.json 的exports字段可见本包按功能拆分了多个独立子路径每个子路径只暴露对应的 codec方便按需加载子路径入口文件导出的编解码器ipfs-message-port-protocolsrc/index.js汇总入口ipfs-message-port-protocol/cidsrc/cid.jsencodeCID/decodeCIDipfs-message-port-protocol/dagsrc/dag.jsencodeNode/decodeNodeipfs-message-port-protocol/coresrc/core.jsencodeIterable/decodeIterable、encodeCallback/decodeCallbackipfs-message-port-protocol/blocksrc/block.jsencodeBlockipfs-message-port-protocol/errorsrc/error.jsencodeError/decodeError核心依赖仅两个multiformatsCID 实现与ipfs-core-types类型定义见 package.json。线协议编解码器Wire protocol codecs本模块为结构化克隆算法无法支持的类型提供编码/解码函数发送端先把这类值编码成可克隆的表示通过消息通道postMessage过去接收端再解码还原。所有编码器的共同约定是可选地接收一个transfer参数一旦提供编码器就会把所有Transferable字段底层ArrayBuffer加入其中使这些内存在线程间被转移而非复制。CID编解码CID 是 IPFS 的标识核心。本包的 CID codec 依赖multiformats/cid完整实现见 src/cid.js。使用示例import { CID, encodeCID, decodeCID } from ipfs-message-port-protocol/cid const cid CID.parse(bafybeig6xv5nwphfmvcnektpnojts33jqcuam7bmye2pb54adnrtccjlsu) const { port1, port2 } new MessageChannel() // 复制底层内存默认行为 port1.postMessage(encodeCID(cid)) // 转移底层内存发送后本线程的 cid 将失效 const transfer [] port1.postMessage(encodeCID(cid, transfer), transfer) // 接收线程侧 port2.onmessage ({ data }) { const cid decodeCID(data) data instanceof CID // true }源码原理encodeCID的实现非常轻量——严格说它并未重新编码而是利用结构化克隆本来就会复制普通属性的事实直接把 CID 对象本身发出去唯一额外动作是若传入了transfer就把cid.multihash.bytes.buffer存放 multihash 字节的ArrayBuffer加入转移列表export const encodeCID (cid, transfer) { if (transfer) { transfer.add(cid.multihash.bytes.buffer) } return cid }decodeCID则相反负责把克隆后“失去类身份”的普通对象重新还原为 CID 实例。它做了一系列原型修复若cid.asCID不存在则定义 getter 返回自身若cid[/]不存在则定义 getter 返回cid.bytes通过Object.setPrototypeOf把multihash.digest、multihash.bytes、bytes重新挂回Uint8Array.prototype最后把整个对象挂回CID.prototype使其重新拥有 CID 类的方法。DAGNode 编解码DAG 节点是ipfs.dag.putAPI 接受的数据结构——任意 JSON 兼容值内部可能嵌套任意层次的 CID 链接。其编解码实现见 src/dag.js。使用示例import { CID } from multiformats/cid import { encodeNode, decodeNode } from ipfs-message-port-protocol/dag const cid CID.parse(QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n) const dagNode { hi: hello, link: cid } const { port1, port2 } new MessageChannel() // 复制底层内存 port1.postMessage(encodeNode(dagNode)) // 转移底层内存本线程 dagNode.link 将失效 const transfer [] port1.postMessage(encodeNode(dagNode, transfer), transfer) // 接收线程侧 port2.onmessage ({ data }) { const dagNode decodeNode(data) dagNode.link instanceof CID // true }源码原理收集式编码encodeNode并不逐字段重写节点而是用collectNode递归遍历整个节点见 src/dag.js#L64-L90遇到CID.asCID(value)为真的对象把它压入cids数组并调用encodeCID转移其底层 buffer遇到ArrayBuffer或其视图typed array若提供了transfer则将其buffer加入转移列表遇到数组则遍历成员遇到普通对象则遍历Object.values递归继续。最终返回{ dagNode, cids }这样的两段式结构dagNode是可直接被结构化克隆的纯 JSON 表示cids是其中所有 CID 的收集清单。export const encodeNode (dagNode, transfer) { const cids [] collectNode(dagNode, cids, transfer) return { dagNode, cids } }decodeNode则只需遍历cids清单逐个调用decodeCID恢复原型无需再次遍历节点本体——这正是收集式编码的意义把“查找并还原 CID”的成本从 O(节点大小) 的遍历降为 O(CID 数量) 的清单迭代export const decodeNode ({ dagNode, cids }) { for (const cid of cids) { decodeCID(cid) } return dagNode }AsyncIterable 编解码远程迭代器AsyncIterable 是 IPFS API 最常见的返回形态如ipfs.cat、ipfs.addAll、ipfs.ls。它无法被结构化克隆因此本包把它编码为一个RemoteIterable一个自带MessagePort的远程迭代器描述接收端通过反复发送next消息逐项拉取数据。实现见 src/core.js#L53-L156。与其他编码器不同AsyncIterable 的transfer参数是必填的——因为异步迭代器被编码成只能转移、不能复制的MessagePort。使用示例import { encodeIterable, decodeIterable } from ipfs-message-port-protocol/core const content ipfs.cat(/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n) const { port1, port2 } new MessageChannel() // 每个 chunk 复制到接收线程 { const transfer [] port1.postMessage( encodeIterable(content, chunk chunk, transfer), transfer ) } // 每个 chunk 转移到底层内存本线程数据失效 { const transfer [] port1.postMessage( encodeIterable( content, (chunk, transfer) { transfer.push(chunk.buffer) return chunk }, transfer ), transfer ) } // 接收线程侧 port2.onmessage async ({ data }) { for await (const chunk of decodeIterable(data)) { chunk instanceof Uint8Array // true } }源码原理encodeIterable(iterable, encode, transfer)内部新建一个MessageChannel把port1留在本地作为服务端、port2即remote加入用户的transfer列表返回{ type: RemoteIterable, port: remote }。服务端监听next/return两类消息next调用iterator.next()推进迭代器若结束则回发{ done: true }并关闭端口否则用itemTransfer预先分配并循环复用的Set见 src/core.js#L104-L129承载当前项的可转移字段调用用户提供的encode(value, itemTransfer)编码该项后回发若迭代器抛错则回发{ type: throw, error: encodeError(error) }return关闭端口并调用iterator.return()触发清理。decodeIterable(remote, decode)则是一个异步生成器每次yield前向端口发送{ method: next }用一个receive变量暂存 Promise 的 resolveport.onmessage到达后兑现见 src/core.js#L53-L90。finally块中若迭代提前结束未done则发送{ method: return }并最终port.close()关闭通道。toIterator同时兼容同步迭代器与异步迭代器见 src/core.js#L163-L175。配套的RemoteYieldT/RemoteDoneT/RemoteError类型把每次next的响应建模为「产出 / 完成 / 抛错」三种形态见 src/core.js#L18-L45错误在传输时经encodeError展平成普通对象、接收时经decodeError按name还原为对应的RangeError/TypeError/Error等原生类型见 src/error.js。Callback 编解码远程回调IPFS 的部分 API 接受回调参数最典型的是ipfs.add的progress进度回调。本包将其编码为RemoteCallback同样自带MessagePort接收端解码得到的函数每次调用都会把参数回传给另一端的真实回调。与 AsyncIterable 相同transfer参数必填。实现见 src/core.js#L182-L206。使用示例import { encodeCallback, decodeCallback } from ipfs-message-port-protocol/core const { port1, port2 } new MessageChannel() const progress (value) console.log(value) const transfer [] port1.postMessage(encodeCallback(progress, transfer), transfer) // 接收线程侧 port2.onmessage ({ data }) { const progress decodeCallback(data) // 将调用另一端的 progress(20) progress(20) }源码原理encodeCallback(callback, transfer)新建MessageChannel让本地端口监听onmessage并把收到的参数数组data通过callback.apply(null, data)派发给真实回调同时把远端端口加入transfer并返回{ type: RemoteCallback, port: remote }。decodeCallback({ port })返回一个包装函数调用时通过postMessage(port, args, transfer)把参数数组发回服务端也支持可选的transfer一并转移参数中的可转移对象。从源码视角看整体协议设计ipfs-message-port-protocol不只是编解码函数集它还定义了跨线程 RPC 的类型骨架供上层的客户端/服务端包共享src/data.ts 定义了JSONValue、EncodedError等基础表示src/rpc.ts 通过 TypeScript 条件类型推导出ServiceQuery、Remote、MultiService等泛型结构把「命名空间 方法 输入 结果」的 RPC 调用模型类型化src/files.ts 与 src/root.ts 声明了 MFS 与 add 相关接口的编码形态如EncodedAddInput、EncodedIPFSEntry、EncodedStat其中文件内容即RemoteIterableArrayBufferView形态的异步可迭代数据。这套协议的实际消费方是同仓库的 packages/ipfs-message-port-client 与 packages/ipfs-message-port-server。例如客户端 packages/ipfs-message-port-client/src/block.js 在调用block.put/block.rm时用encodeCID编码 CID、用decodeCID还原结果packages/ipfs-message-port-client/src/dag.js 用encodeNode/decodeNode处理 DAG 节点packages/ipfs-message-port-client/src/core.js 则组合使用encodeIterable/decodeIterable/encodeCallback实现cat、addAll、ls等异步迭代 API 与进度回调的跨线程封装。测试端可通过aegir test含 node、browser、webworker 多种目标见 package.json验证协议在真实 Worker 环境下的行为。实战要点小结复制还是转移不传transfer则所有底层内存被复制安全但开销大传入transfer则零拷贝转移快但发送端数据即刻失效。跨线程传大数据如文件块、大 DAG务必走转移路径。transfer是必填还是可选CID / DAGNode / Block 的编码器可选AsyncIterable 与 Callback 因为要传MessagePort必填。成对使用encode*与decode*必须配套且解码端使用的decode函数要与编码端为每项提供的encode函数语义一致如均按Uint8Array处理。端口生命周期AsyncIterable 传输结束后端口自动关闭next到达末尾或提前return接收端的for await...of正常退出即可无需手动关闭回调型端口在不再需要时应由上层负责关闭。错误传递迭代器中的异常经encodeError展平后跨线程传输解码端按错误名还原为对应原生错误类型调用方可用try/catch正常捕获。License 与参与方式本包以 Apache 2.0 与 MIT 双许可发布见 LICENSE-APACHE 与 LICENSE-MIT除非明确说明所有有意提交的贡献均按 Apache-2.0 定义以双重许可纳入。参与贡献请遵循仓库的 CONTRIBUTING.md 与 IPFS 社区行为准则。赞分享存储网络通信【免费下载链接】js-ipfsIPFS implementation in JavaScript项目地址https://gitcode.com/gh_mirrors/js/js-ipfs点击查看免费下载相关推荐Typer 开发指南用 Docker 容器分步测试 Bash/Zsh/Fish/PowerShell 补全功能Typer 开发指南用 Docker 容器分步测试 Bash/Zsh/Fish/PowerShell 补全功能 导读 本文基于 Typer 仓库的 docs/存储网络通信js-IPFS gRPC 协议包 ipfs-grpc-protocol 深度解析Protobuf 定义、消息结构与生成流程js IPFS gRPC 协议包 ipfs grpc protocol 深度解析Protobuf 定义、消息结构与生成流程 导读 ipfs grpc prot存储网络通信ipfs-message-port-server 实战指南通过 MessageChannel 将 js-IPFS 节点暴露给多客户端ipfs message port server 实战指南通过 MessageChannel 将 js IPFS 节点暴露给多客户端 ipfs message存储网络通信上一篇联想拯救者Y7000系列BIOS解锁工具一键开启隐藏高级设置权限下一篇MoneyPrinterPlus项目解析AI短视频批量生成与编辑全攻略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表