本指南解释 Solana 的 PubSub WebSocket API,涵盖订阅方法、连接生命周期和常见故障。包括使用 @solana/web3.js 的可运行 Node.js 示例和故障排除清单。
直接回答:什么是 Solana RPC WebSocket?
Solana 的 RPC WebSocket API(也称为 PubSub)允许您通过持久 WebSocket 连接接收有关账户更改、程序活动和交易日志的实时通知。与轮询标准 JSON-RPC HTTP 端点不同,WebSocket 订阅会在数据可用时立即将数据推送到您的客户端,从而减少延迟和负载。官方端点是 ws://<ADDRESS>/ 或 wss://<ADDRESS>/(例如,本地验证器的 ws://localhost:8899,或公共 RPC 提供商的 wss URL)。本指南涵盖三种主要订阅方法——accountSubscribe、programSubscribe 和 logsSubscribe——以及槽位和根订阅,并通过可运行的 Node.js 示例解释完整的连接生命周期。
Solana 的 WebSocket API 是基于 WebSocket 的 JSON-RPC 2.0,采用请求/响应模式进行订阅,并使用通知格式传入事件。它与 EVM 风格的 eth_subscribe 不同,因为 Solana 的模型以账户为中心,并使用基于领导者调度的通知时序。理解这些差异对于构建可靠的实时应用程序至关重要。
- 端点系列:
ws://localhost:8899用于本地开发,wss://用于公共 RPC 提供商(例如wss://api.mainnet-beta.solana.com)。 - 三种主要订阅方法:
accountSubscribe、programSubscribe、logsSubscribe。 - 其他方法:
slotSubscribe、rootSubscribe和signatureSubscribe(尽管签名通知通常通过轮询处理)。 - 通过
accountUnsubscribe、programUnsubscribe、logsUnsubscribe等取消订阅。
Solana PubSub 如何工作:架构和通知流程
Solana 的 PubSub API 基于 JSON-RPC 2.0。要订阅,您发送一个包含方法名称和参数的请求,服务器响应一个订阅 ID。此后,每当订阅的事件发生时,服务器会向客户端发送通知消息(JSON-RPC 2.0 通知)。每个通知都包含订阅 ID 和数据负载。
通知节奏与 Solana 的领导者调度相关。对于 accountSubscribe 和 programSubscribe,当账户或程序数据发生变化时发送通知,但仅在槽位被确认后。对于 logsSubscribe,每当触及订阅程序或账户的交易发出日志消息时发送通知。这意味着您可能会在领导者变更时看到通知突发,并且与实际交易处理相比可能会有轻微延迟。
官方 Solana 文档(Solana RPC WebSocket 方法)指定了确切的请求和通知格式。例如,订阅请求如下:{"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["PUBKEY",{"encoding":"base64","commitment":"confirmed"}]}。响应是 {"jsonrpc":"2.0","result":"SUBSCRIPTION_ID","id":1}。然后通知到达,格式为 {"jsonrpc":"2.0","method":"accountNotification","params":{"result":{"context":{"slot":123},"value":{...}},"subscription":"SUBSCRIPTION_ID"}}。
- 请求格式:JSON-RPC 2.0 请求,包含方法、参数和 id。
- 响应格式:JSON-RPC 2.0 响应,包含结果(订阅 ID)和 id。
- 通知格式:JSON-RPC 2.0 通知,包含方法(例如
accountNotification)和包含订阅 ID 和结果的参数。 - 承诺级别:
processed、confirmed、finalized影响通知发送的时间。
订阅方法深入探讨
三种主要订阅方法是 accountSubscribe、programSubscribe 和 logsSubscribe。每种方法都有特定的参数和通知负载。
accountSubscribe 监视单个账户的状态变化。参数:账户公钥(base58 字符串)和可选配置对象,包含 commitment 和 encoding。通知负载包括账户的数据、lamports、所有者、可执行标志、租金纪元以及槽位上下文。
programSubscribe 监视程序拥有的所有账户。参数:程序公钥和可选配置,包含 encoding 和 filters(例如 dataSize 或 memcmp)。通知类似于账户通知,但针对程序拥有的任何账户。
logsSubscribe 监视交易日志。参数:过滤器("all"、{"mentions": [pubkey]} 用于账户或程序提及,或 {"mentions": [pubkey], "commitment": "confirmed"})和可选承诺。通知包括日志数组、签名和槽位。
此外,slotSubscribe 和 rootSubscribe 提供槽位和根更新,用于跟踪链进度。signatureSubscribe 也可用,但通常较少使用,因为它需要预先知道签名。
- accountSubscribe:
params: [pubkey, {encoding, commitment}] - programSubscribe:
params: [programId, {encoding, filters, commitment}] - logsSubscribe:
params: [filter, {commitment}],其中 filter 是"all"或{"mentions": [pubkey]} - slotSubscribe:
params: [] - rootSubscribe:
params: [] - 取消订阅方法:
accountUnsubscribe、programUnsubscribe、logsUnsubscribe等。
使用 @solana/web3.js 的可运行 Node.js 示例
使用 Solana WebSocket 订阅的最简单方法是通过 @solana/web3.js 库,它封装了原始 WebSocket API。Connection 类提供了 onAccountChange、onProgramAccountChange 和 onLogs 等方法,自动处理订阅和通知解析。
下面是一个完整示例,订阅给定地址的账户更改、程序 ID 的程序账户更改以及程序的日志。它还演示了如何处理重连和清理。要运行它,请安装 @solana/web3.js 和 ws(用于 Node.js 中的 WebSocket)。
预期输出:脚本将打印订阅 ID,然后记录到达的通知。由于实时数据取决于网络活动,您可能需要触发一些交易才能看到通知。该示例包含一个 30 秒后退出的超时。
- 使用带有 WebSocket URL 的
Connection(例如wss://api.mainnet-beta.solana.com)。 onAccountChange返回订阅 ID(数字)。onProgramAccountChange接受程序 ID 和可选过滤器。onLogs接受过滤器(例如'all'或{mentions: [programId]})。- 始终处理 WebSocket 上的
error和close事件以实现重连。
// 安装:npm install @solana/web3.js ws
const { Connection, PublicKey } = require('@solana/web3.js');
// 替换为您的端点(例如 wss://api.mainnet-beta.solana.com)
const wsUrl = 'wss://api.mainnet-beta.solana.com';
const connection = new Connection(wsUrl, 'confirmed');
// 示例:订阅已知代币账户的账户更改
const accountPubkey = new PublicKey('YOUR_ACCOUNT_PUBKEY');
const subId = connection.onAccountChange(accountPubkey, (accountInfo, context) => {
console.log('槽位', context.slot, '的账户更改');
console.log('Lamports:', accountInfo.lamports);
console.log('数据长度:', accountInfo.data.length);
}, 'confirmed');
// 订阅程序账户更改(例如 SPL Token 程序)
const programId = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
const programSubId = connection.onProgramAccountChange(programId, (accountInfo, context) => {
console.log('槽位', context.slot, '的程序账户更改');
}, 'confirmed');
// 订阅同一程序的日志
const logsSubId = connection.onLogs(programId, (logs, context) => {
console.log('槽位', context.slot, '的日志');
console.log('签名:', logs.signature);
console.log('日志:', logs.logs);
}, 'confirmed');
console.log('已订阅,ID:', subId, programSubId, logsSubId);
// 保持进程存活并处理清理
setTimeout(() => {
connection.removeAccountChangeListener(subId);
connection.removeProgramAccountChangeListener(programSubId);
connection.removeOnLogsListener(logsSubId);
console.log('已取消订阅并退出。');
process.exit(0);
}, 30000);
// 预期输出(示例):
// 已订阅,ID: 1 2 3
// 槽位 123456 的账户更改
// Lamports: 2039280
// 数据长度: 165
// ...(通知按发生顺序出现)连接生命周期:保持活动、重连和重新订阅
与 Solana PubSub 端点的 WebSocket 连接不是永久的。它可能因网络问题、服务器重启或空闲超时而断开。要构建健壮的客户端,您必须处理连接生命周期:连接、保持活动、检测关闭和重新订阅。
保持活动:大多数 WebSocket 服务器会定期发送 ping 帧。在 Node.js 中,ws 库自动响应 ping,但您也可以发送应用级 ping。Solana 官方文档未指定保持活动间隔,但通常每 30 秒发送一次 ping 以防止空闲超时。如果您使用 @solana/web3.js,该库在内部处理 ping,但您仍应监听 close 事件。
重连:当连接关闭时,您需要重新连接并重新建立所有订阅。@solana/web3.js 的 Connection 类不会自动重新订阅;您必须自己实现。常见模式是将订阅设置包装在函数中,并在 reconnect 时调用它。您可以使用 reconnecting-websocket 之类的库或编写自己的逻辑。
重新订阅:重新连接后,您必须再次调用订阅方法。跟踪您的订阅 ID 和使用的参数,以便使用相同的过滤器重新订阅。请注意,重新连接后订阅 ID 可能会更改。
有关通用 WebSocket 断开连接修复的深入探讨,请参阅我们的通用 WebSocket RPC 断开连接修复。
- 监听
open、message、error和close事件。 - 为重连尝试实现指数退避(例如 1 秒、2 秒、4 秒,最大 30 秒)。
- 重新连接时,重新订阅所有活动订阅。
- 使用心跳(ping/pong)检测死连接。
- 考虑使用
reconnecting-websocket之类的库进行自动重连。
// 使用 ws 和 @solana/web3.js 的重连逻辑示例
const WebSocket = require('ws');
const { Connection } = require('@solana/web3.js');
let connection;
let subscriptions = [];
function setupSubscriptions() {
// 清除旧订阅
subscriptions.forEach(id => connection.removeAllListeners(id));
subscriptions = [];
// 重新订阅
const subId = connection.onAccountChange(accountPubkey, callback, 'confirmed');
subscriptions.push(subId);
// ... 添加其他订阅
}
function connect() {
connection = new Connection(wsUrl, 'confirmed');
connection._ws.on('close', () => {
console.log('连接关闭。5 秒后重新连接...');
setTimeout(connect, 5000);
});
connection._ws.on('open', () => {
console.log('已连接。正在设置订阅。');
setupSubscriptions();
});
}
connect();常见故障和故障排除清单
即使实现扎实,您也可能遇到问题。以下是常见故障及其修复方法。
负载下断开:高吞吐量订阅可能会压垮您的客户端或服务器。如果您看到频繁断开,请减少订阅数量或使用过滤器缩小数据范围。同时确保您的客户端快速处理消息;如果回调缓慢,可能会阻塞事件循环并导致超时。
通知延迟:通知与领导者调度和承诺级别相关。如果您需要更快的更新,请使用 processed 承诺,但请注意数据可能会被回滚。对于最终数据,请使用 confirmed 或 finalized。
过滤器误用:对于 programSubscribe,dataSize 和 memcmp 等过滤器必须正确格式化。memcmp 需要 offset 和 bytes(base58 编码)。如果您没有收到通知,请仔细检查过滤器逻辑。
连接错误:常见错误包括 Unexpected server response: 403(如果端点需要 API 密钥)或 WebSocket is closed before the connection is established。确保您的 URL 正确并且您有网络访问权限。
订阅 ID 未找到:如果您尝试使用无效 ID 取消订阅,将收到错误。跟踪 ID 并正确移除监听器。
- 检查您的端点 URL:使用
wss://进行安全连接,并确保其可访问。 - 验证承诺级别:
processed最快但可靠性较低;confirmed是良好的默认值。 - 使用过滤器减少数据量:程序订阅使用
dataSize和memcmp。 - 监控您的 WebSocket 连接状态并实现带退避的重连。
- 使用本地验证器(
solana-test-validator)进行测试以避免速率限制。 - 如果使用公共 RPC 提供商,请查看其文档了解速率限制和 WebSocket 特定规则。
权衡和限制
Solana 的 PubSub API 功能强大但也有权衡。通知不保证有序或完整;如果连接断开,您可能会错过事件。此外,通知节奏取决于领导者调度,因此您可能会看到活动突发而不是稳定流。
端点保留:公共 RPC 提供商可能对 WebSocket 连接有不同的保留策略。有些可能会在一定时间后断开空闲连接。始终实现重连逻辑。
通知时序因提供商和网络负载而异。对于关键应用程序,不要依赖精确时序;使用承诺级别来平衡速度和可靠性。
与 EVM eth_subscribe 相比,Solana 的模型以账户为中心,需要理解 Solana 的账户模型。例如,programSubscribe 类似于合约事件的 eth_subscribe,但作用于账户状态更改。
对于生产环境,考虑使用具有高可用性的专用 WebSocket 提供商。OnFinality 的 API 服务 提供可靠的 WebSocket 端点,您可以在我们的 RPC Assistant 中比较 Solana RPC 端点。
- 通知不保证无丢失;如果需要,请实现自己的对账。
- 公共端点可能有速率限制;请查看您的提供商的文档。
- WebSocket 连接是有状态的;生产环境必须重连。
- 对于不可逆数据,使用
finalized承诺,但预期更高的延迟。 - 对于高吞吐量用例,考虑使用专用流服务,如 Helius LaserStream(独立第三方)。
后续步骤和进一步阅读
既然您了解了 Solana 的 WebSocket API,您可以构建实时应用程序,如交易监控器、投资组合跟踪器或套利机器人。从官方 Solana RPC WebSocket 方法 文档开始,了解确切的 JSON 格式。
如果您是 Solana 开发新手,请探索我们的 Solana 网络指南 以获取概述。有关定价考虑,请参阅 RPC 定价。如果您遇到断开连接问题,请参阅我们的 通用 WebSocket RPC 断开连接修复。
要更广泛地了解 RPC 服务,请访问 OnFinality Learn 中心 获取更多教程和指南。
- 官方 Solana WebSocket 文档:solana.com/docs/rpc/websocket
- Solana RPC 概述:docs.solana.com/api
- OnFinality 的 Solana RPC 端点:RPC Assistant
- 探索我们的 API 服务 获取托管的 WebSocket 端点。