Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
RPC 故障排查阅读约 14 分钟

JSON-RPC 在 HTTP、WebSocket 与 IPC 上的传输选择

了解 JSON-RPC 2.0 语义在 HTTP、WebSocket 和 IPC 传输中的差异,以及如何为生产级区块链应用选择和组合这些传输方式。

TL;DR

JSON-RPC 2.0 与传输方式无关:相同的请求/响应/通知信封可以通过 HTTP、WebSocket 或 IPC 传输,但每种传输方式施加不同的连接语义、顺序保证和故障模式。HTTP 是每消息一请求,依赖批处理提升吞吐量;WebSocket 是单一多路复用连接,支持订阅和通知,但需要 id 关联和重新订阅逻辑;IPC 适合节点运营者,但不适合必须在节点重启后存活的应用程序。本文解释分层模型,提供决策表、可运行的 Node.js 示例以及评估提供商的验证清单。

分层模型:JSON-RPC 语义与传输行为

集成区块链 RPC 时最常见的困惑来源是将 JSON-RPC 层与传输层混为一谈。JSON-RPC 2.0 规范定义了一种与传输无关的协议:它处理请求对象、响应对象和通知,每个都包含 jsonrpc 成员、用于关联的 id,以及 method/paramsresult/error 负载。传输层——HTTP、WebSocket 或 IPC——处理连接建立、消息分帧、顺序、背压,以及 HTTP 的状态码。

这种分离很重要,因为错误处理存在于两个不同的地方。JSON-RPC 错误是一个结构化对象,包含数字 code(例如 -32601 表示 Method not found)和 message,在有效的 JSON-RPC 响应中返回。像 429、502 或 504 这样的 HTTP 状态码是传输层信号,表示请求从未到达 JSON-RPC 处理器,或者连接失败。429 永远不会携带 -32603,-32603 也永远不会携带传输状态。当你看到 200 OK 且内部包含错误对象时,那是 JSON-RPC 层在说话;当你看到 502 且响应体是 HTML 时,那是传输层在 JSON-RPC 语义生效之前就失败了。

以太坊 JSON-RPC 规范在此基础上定义了执行层方法及其预期参数和返回类型,但并未重新定义传输语义。提供商可能会记录额外行为——例如批处理限制或 WebSocket 空闲超时——但这些是实现细节,不是协议要求。始终将提供商特定行为视为有文档说明/因提供商而异,并根据你自己的测量进行验证。

  • JSON-RPC 层:请求/响应/通知信封、数字错误码、id 关联。
  • 传输层:连接生命周期、分帧、顺序、HTTP 状态码、背压。
  • 200 OK 且包含错误对象是 JSON-RPC 错误;502 且没有 JSON 响应体是传输失败。
  • 提供商特定限制(批处理大小、空闲超时)不属于 JSON-RPC 2.0 规范。

HTTP 作为每消息一请求的传输方式及批处理的作用

HTTP 是每消息一请求的传输方式:每个 JSON-RPC 请求通常对应一个 HTTP POST。协议不强制要求连接复用,尽管 HTTP/1.1 keep-alive 和 HTTP/2 多路复用可以减少 TCP 握手开销。通过 HTTP 提升吞吐量的关键杠杆不是连接复用,而是批处理——在单个 HTTP 请求中发送请求对象数组,如 JSON-RPC 2.0 规范第 6 节所定义。

批处理有重要的细微差别。批处理在传输线上是原子的——所有请求一起到达——但结果不是:服务器可以按任意顺序处理它们,有些可能成功,有些可能失败。响应是响应对象数组,客户端必须通过 id 进行关联。根据规范,空数组是 Invalid Request。服务器可能施加最大批处理大小;超过时,它们通常返回 -32000 到 -32099 范围内的实现定义错误码(保留给实现定义的服务器错误),而不是像 -32600 这样的规范定义错误码。始终检查提供商文档以了解批处理限制和返回的确切错误码。

对于高容量索引或分析工作负载,批处理可以显著减少 HTTP 开销。然而,它也增加了单个失败请求的影响范围:如果 HTTP 请求在传输层失败,整个批处理都会丢失。设计客户端时,要考虑幂等性,以便重试单个请求或整个批处理。

  • HTTP 是每消息一请求;批处理是主要的吞吐量杠杆。
  • 批处理响应是数组;通过 id 关联,因为顺序不保证。
  • 根据 JSON-RPC 2.0 第 6 节,空批处理数组是 Invalid Request。
  • 批处理大小限制是实现定义的;超过时预期 -32000 范围错误码。

WebSocket 作为多路复用传输:订阅与通知

WebSocket 提供单一、持久、全双工的连接。这以两种根本方式改变了 JSON-RPC 契约。首先,响应可能相对于请求乱序到达,因为同一套接字上可以同时有多个请求在途。id 关联变得强制——你不能假设第一个响应匹配第一个请求。其次,WebSocket 支持服务器发起的消息:通知,即没有 id 的 JSON-RPC 请求(规范第 4.1 节),以及订阅推送。

在以太坊执行层 JSON-RPC 中,eth_subscribe 是一个方法,返回包含订阅 id 的正常响应。之后,节点推送 eth_subscription 通知。关键的是,这些通知的关联键位于 params.subscription 中,而不是请求 id 中。只跟踪请求 id 的客户端将无法正确路由订阅事件。这是一个常见的集成 bug。

WebSocket 的持久性也意味着套接字断开会静默丢失所有在途请求和所有活动订阅。没有自动重放。生产客户端必须实现重新订阅和回填逻辑:检测断开、重新建立连接、重新发出 eth_subscribe 调用,并使用 HTTP 或 WebSocket 请求获取任何错过的区块或日志以填补缺口。这是传输设计的一部分,不是错误处理器。

  • 响应乱序到达;id 关联是强制性的。
  • eth_subscribe 在正常响应中返回订阅 id;事件作为 eth_subscription 通知到达,关联在 params.subscription 中。
  • 套接字断开会丢失所有在途请求和订阅;需要重新订阅和回填。
  • 通知没有 id,不得回复。

IPC 与进程内传输:节点运营者与应用程序的权衡

IPC(进程间通信)传输,例如 Unix 域套接字或 Windows 命名管道,是本地的。它们提供低开销且没有网络栈,非常适合运行全节点并希望从同机进程查询它的节点运营者。然而,对于必须在节点重启后存活或跨主机扩展的应用程序来说,它们是错误的选择。IPC 没有 TLS 终止、没有跨主机故障转移,通常每个客户端只允许一个连接。如果你的应用程序运行在容器或不同机器上,IPC 不是选项。

对于自托管节点,将 IPC 暴露给共享后端可能因性能而诱人,但它将应用程序的可用性与单个节点进程耦合。如果该节点重启,所有 IPC 客户端都会失去连接并必须重新连接。没有内置的负载均衡或故障转移。对于需要高可用性的生产应用程序,HTTP 或 WebSocket 端点——通常由托管服务提供——更合适。如果你正在评估提供商,请参阅 RPC 端点指南(RPC Assistant) 了解标准。

进程内传输(例如在同一进程内直接调用节点的 RPC 处理器)耦合更紧密,通常仅用于测试或嵌入式场景。它们绕过网络序列化,但继承相同的单点故障特征。

  • IPC 仅限本地,无 TLS,无跨主机故障转移,通常每个客户端一个连接。
  • 适合节点运营者查询同机节点;不适合分布式应用程序。
  • 自托管 IPC 暴露将应用程序可用性与单个节点进程耦合。
  • 对于高可用性,优先选择托管提供商的 HTTP 或 WebSocket 端点。

区块链 JSON-RPC 方法的传输一致性

并非所有 JSON-RPC 方法都可通过所有传输方式使用。常见的区块链方法集——状态查询如 eth_getBalance、历史查询如 eth_getBlockByNumber、交易提交如 eth_sendRawTransaction——是普通的请求/响应调用,在 HTTP、WebSocket 和 IPC 上同样工作。然而,订阅方法如 eth_subscribeeth_unsubscribe 仅存在于有状态传输上:WebSocket 和 IPC。它们不能通过 HTTP 使用,因为 HTTP 是每消息一请求,无法支持服务器发起的推送。

这意味着从 WebSocket 故障转移到 HTTP 进行订阅的客户端必须改变策略,而不仅仅是端点。它不能简单地通过 HTTP 重新发出 eth_subscribe;它必须切换到轮询——例如使用带有移动区块范围的 eth_getLogs——或者使用支持 WebSocket 的其他提供商。eth_subscribe 日志与 WebSocket 轮询 文章详细介绍了这种权衡。

类似地,一些 admin 或 debug 方法可能出于安全原因仅限于 IPC,即使它们在技术上是请求/响应。始终验证你的提供商在每个传输上暴露了哪些方法。

  • 状态和历史方法在 HTTP、WebSocket 和 IPC 上工作。
  • 订阅方法(eth_subscribeeth_unsubscribe)需要有状态传输(WebSocket 或 IPC)。
  • 从 WebSocket 故障转移到 HTTP 进行订阅需要切换到轮询,而不仅仅是更改 URL。
  • Admin/debug 方法可能出于安全原因仅限 IPC。

决策表:将工作负载形态映射到传输方式

选择传输方式是工作负载形态的函数。下表将常见模式映射到推荐的传输方式及每种选择背后的原因。将其作为起点,然后根据你自己的延迟和可靠性测量进行验证。

对于单次读取(例如在显示余额之前获取余额),HTTP 简单且足够。对于高容量批量索引(例如为分析管道回填日志),带批处理的 HTTP 通常最高效。对于事件流(例如实时交易监控),带 eth_subscribe 的 WebSocket 是自然选择。对于交易提交,HTTP 和 WebSocket 都可以,但 HTTP 可能因其简单性和幂等重试语义而更受青睐。对于 admin/debug 内省,IPC 通常是自托管节点上的唯一选项。

  • 单次读取:HTTP——简单、无状态、易于重试。
  • 高容量批量索引:带批处理的 HTTP——减少每请求开销。
  • 事件流:WebSocket——支持 eth_subscribe 和服务器推送。
  • 交易提交:HTTP 或 WebSocket——HTTP 简单,WebSocket 在已连接时延迟更低。
  • Admin/debug 内省:IPC——特权方法通常需要。

可运行示例:通过 HTTP 和 WebSocket 发送相同请求

以下 Node.js 脚本通过 HTTP 和 WebSocket 发出相同的 eth_blockNumber 请求,打印传输方式、在你的机器上测量的往返时间以及每个响应回显的原始 id。这使关联契约可见而非假设。针对你自己的端点运行它以比较行为。注意 WebSocket 示例使用 ws 包;如需要,使用 npm install ws 安装。

该脚本使用 process.hrtime.bigint() 进行高分辨率往返时间测量。id 设置为每个请求的唯一值,以便你可以看到它被回显。对于 WebSocket,如果有多个请求在途,响应可能在其他消息之后到达;为清晰起见,此示例一次发送一个请求。

const http = require('http');
const WebSocket = require('ws');

const HTTP_URL = 'https://ethereum.publicnode.com';
const WS_URL = 'wss://ethereum.publicnode.com';

function httpRequest(url, payload) {
  return new Promise((resolve, reject) => {
    const start = process.hrtime.bigint();
    const req = http.request(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } }, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        const end = process.hrtime.bigint();
        const rttMs = Number(end - start) / 1e6;
        resolve({ transport: 'HTTP', rttMs, response: JSON.parse(data) });
      });
    });
    req.on('error', reject);
    req.write(JSON.stringify(payload));
    req.end();
  });
}

function wsRequest(url, payload) {
  return new Promise((resolve, reject) => {
    const ws = new WebSocket(url);
    const start = process.hrtime.bigint();
    ws.on('open', () => ws.send(JSON.stringify(payload)));
    ws.on('message', (data) => {
      const end = process.hrtime.bigint();
      const rttMs = Number(end - start) / 1e6;
      ws.close();
      resolve({ transport: 'WebSocket', rttMs, response: JSON.parse(data) });
    });
    ws.on('error', reject);
  });
}

(async () => {
  const payload = { jsonrpc: '2.0', method: 'eth_blockNumber', params: [], id: 1 };
  const httpResult = await httpRequest(HTTP_URL, payload);
  const wsResult = await wsRequest(WS_URL, payload);
  console.log('HTTP:', httpResult.transport, 'RTT:', httpResult.rttMs.toFixed(2), 'ms', 'id:', httpResult.response.id, 'result:', httpResult.response.result);
  console.log('WebSocket:', wsResult.transport, 'RTT:', wsResult.rttMs.toFixed(2), 'ms', 'id:', wsResult.response.id, 'result:', wsResult.response.result);
})();

标准化前需记录的操作限制与权衡

在标准化单一传输方式之前,记录影响可靠性和性能的操作限制。代理和负载均衡器通常会终止空闲连接;看起来健康的 WebSocket 连接可能在空闲一段时间后被静默丢弃。每连接状态使得简单的轮询故障转移对订阅不安全:如果你在负载均衡器后面有多个 WebSocket 连接,在一个连接上创建的订阅不会在另一个连接上接收事件。你必须使用粘性会话或实现订阅管理器来跟踪哪个连接持有哪个订阅。

提供商的 HTTP 和 WebSocket 端点可能不由同一集群提供服务。这意味着测量的延迟和可用命名空间可能不同。例如,HTTP 端点可能由针对查询优化的只读副本集群提供服务,而 WebSocket 端点由另一组支持订阅的节点提供服务。始终独立测试两个端点。有关监控指导,请参阅 监控 RPC 端点

最后,考虑维护多种传输方式的成本和复杂性。虽然将 HTTP 用于查询、WebSocket 用于订阅很常见,但这使配置、认证和监控的表面积加倍。权衡收益与操作开销。

  • 代理/负载均衡器终止空闲连接可能静默丢弃 WebSocket 连接。
  • 轮询故障转移对订阅不安全;使用粘性会话或订阅管理器。
  • HTTP 和 WebSocket 端点可能由不同集群提供服务;延迟和命名空间可能不同。
  • 维护多种传输方式增加配置和监控复杂性。

传输特定故障的故障排查

当请求失败时,首先确定是哪一层报告错误。如果你收到像 429、502 或 504 这样的 HTTP 状态码,失败在传输或网关层;JSON-RPC 请求可能未被处理。检查提供商的状态页面并使用指数退避重试。如果你收到 200 OK 且包含 JSON-RPC 错误对象,请求到达了处理器但被拒绝——检查 error.codeerror.message 了解详情。

对于 WebSocket,常见问题包括静默断开、错过订阅事件和 id 关联 bug。实现心跳(ping/pong)以检测死连接。确保客户端跟踪来自 eth_subscribe 响应的订阅 id,并通过 params.subscription 路由 eth_subscription 通知。如果你看到重复或缺失事件,检查你的重新订阅逻辑是否正确回填。JSON-RPC id 关联与批处理顺序 文章提供了更深入的指导。

对于 HTTP 批处理,如果你收到 -32000 范围内的错误码,你可能超过了提供商的批处理限制。减少批处理大小或拆分为多个请求。如果发送空数组,预期 Invalid Request 错误。始终根据 JSON-RPC 2.0 规范验证你的批处理负载。

  • HTTP 429/502/504:传输/网关失败;使用退避重试。
  • 200 OK 且包含 JSON-RPC 错误:处理器级拒绝;检查 error.code。
  • WebSocket:实现心跳、跟踪订阅 id、重连时回填。
  • 批处理错误在 -32000 范围:可能超过批处理限制;减少批处理大小。

评估 RPC 提供商的验证清单

使用此清单评估你考虑的每个提供商。它侧重于影响生产应用程序的传输能力和限制。将你的发现记录在结果表中以进行比较。

针对 HTTP 和 WebSocket 端点运行这些测试。对于批处理支持,发送逐渐增大的批处理直到遇到错误;记录最大大小和错误码。对于订阅,尝试使用 newHeadslogseth_subscribe 并验证你收到通知。对于空闲超时,打开 WebSocket 连接并等待不发送消息;记录它何时关闭。对于命名空间文档,检查提供商文档了解每个传输上可用的方法。

  • HTTP 是否支持批处理?批处理上限是多少,超过时返回哪个错误码?
  • WebSocket 端点是否支持你所需方法的 eth_subscribe
  • 从你自己的网络观察到的空闲超时是多少?
  • 提供商是否记录了两种端点的命名空间?
  • 从你的部署区域测量两种传输上简单 eth_blockNumber 的往返时间。
| Item / Step | Input Variant | Observed Code | Observed Message | HTTP Status | Elapsed ms |
| --- | --- | --- | --- | --- | --- |
| eth_blockNumber over HTTP | ____ | ____ | ____ | ____ | ____ |
| eth_blockNumber over WebSocket | ____ | ____ | ____ | ____ | ____ |
| Batch size cap test | ____ | ____ | ____ | ____ | ____ |
| eth_subscribe newHeads | ____ | ____ | ____ | ____ | ____ |

下一步:为生产组合传输方式

健壮的生产架构通常组合传输方式:HTTP 用于无状态查询和批处理,WebSocket 用于订阅和低延迟推送。使用连接管理器处理重连、重新订阅和回填。对于高可用性,考虑多个提供商和尊重传输差异的故障转移逻辑。如果你在以太坊上构建,请从 以太坊网络页面 开始,并探索 OnFinality Learn 中心 获取更多集成指南。

选择提供商时,查看 RPC 定价API 服务 了解限制和支持。有关连接复用和 keep-alive 策略,请参阅 RPC 连接复用与 HTTP/2 keep-alive。始终使用你自己的工作负载进行测试,并针对你自己的端点进行测量。

最后,将 JSON-RPC 2.0 规范和以太坊 JSON-RPC 规范作为权威参考。提供商文档有用但可能有所不同;协议语义是稳定的。

  • 组合 HTTP 用于查询和 WebSocket 用于订阅。
  • 实现重连、重新订阅和回填逻辑。
  • 测试多个提供商并测量你所在区域的延迟。
  • 参考 JSON-RPC 2.0 规范和以太坊 JSON-RPC 规范获取权威语义。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始