什么是WebSockets及其用途?

WebSockets在您的应用程序和Solana区块链之间提供了一个持久的实时连接。与传统的HTTP请求中反复询问“是否有变化?”不同,WebSockets让区块链在发生变化时立即通知您。

实时更新

当账户变化、交易发生或区块生成时立即收到通知

高效的资源使用

一个持久连接代替数百个HTTP请求的轮询

低延迟

亚秒级响应时间,适用于时间关键的交易和监控应用

标准协议

使用Solana的官方WebSocket API - 兼容所有Solana工具

核心概念

订阅类型

承诺级别

了解承诺级别对于可靠的应用程序至关重要:
最快 - 交易已被验证者处理但未确认
  • 延迟:约400毫秒
  • 风险:可能被丢弃或重新排序
  • 用于:实时UI更新(需谨慎)
想了解更多关于承诺级别的信息吗? 阅读我们的综合博客文章:理解 Solana 承诺级别

可用的 WebSocket 方法

本指南中的示例涵盖了最常用的 WebSocket 方法,但 Solana 的 WebSocket API 提供了更多的订阅类型以满足特殊用例。

完整的 API 参考

探索所有18+ WebSocket 方法。每个方法都包括详细的参数、响应格式和示例。

实现强大的重连逻辑

WebSocket 连接可能由于各种原因断开 - 网络问题、服务器维护或临时中断。生产应用程序必须实现重连逻辑以确保可靠性。

为什么会发生断连

检测断开连接

class ConnectionMonitor {
  constructor(ws) {
    this.ws = ws;
    this.pingInterval = null;
    this.lastPong = Date.now();
    this.isConnected = false;
    
    this.setupEventListeners();
    this.startPingMonitoring();
  }
  
  setupEventListeners() {
    this.ws.onopen = () => {
      console.log('Connected');
      this.isConnected = true;
      this.lastPong = Date.now();
    };
    
    this.ws.onclose = (event) => {
      console.log('Disconnected:', event.code, event.reason);
      this.isConnected = false;
      this.stopPingMonitoring();
    };
    
    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
      this.isConnected = false;
    };
    
    // Listen for pong responses (server acknowledgment)
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.method === 'pong') {
        this.lastPong = Date.now();
      }
      // Handle other messages...
    };
  }
  
  startPingMonitoring() {
    this.pingInterval = setInterval(() => {
      if (this.isConnected) {
        // Send ping to check connection health
        this.ws.send(JSON.stringify({
          jsonrpc: '2.0',
          method: 'ping',
          id: Date.now()
        }));
        
        // Check if we received a pong recently
        const timeSinceLastPong = Date.now() - this.lastPong;
        if (timeSinceLastPong > 30000) { // 30 seconds timeout
          console.warn('No pong received, connection may be stale');
          this.ws.close();
        }
      }
    }, 10000); // Ping every 10 seconds
  }
  
  stopPingMonitoring() {
    if (this.pingInterval) {
      clearInterval(this.pingInterval);
      this.pingInterval = null;
    }
  }
}

重连策略

class ExponentialBackoffReconnector {
  constructor(url, maxRetries = 10) {
    this.url = url;
    this.maxRetries = maxRetries;
    this.retryCount = 0;
    this.baseDelay = 1000; // Start with 1 second
    this.maxDelay = 30000; // Cap at 30 seconds
    this.ws = null;
    this.subscriptions = new Map();
    this.isReconnecting = false;
  }
  
  connect() {
    if (this.isReconnecting) return;
    
    try {
      this.ws = new WebSocket(this.url);
      this.setupEventHandlers();
    } catch (error) {
      console.error('Failed to create WebSocket:', error);
      this.scheduleReconnect();
    }
  }
  
  setupEventHandlers() {
    this.ws.onopen = () => {
      console.log('Connected successfully');
      this.retryCount = 0; // Reset retry count on successful connection
      this.isReconnecting = false;
      this.resubscribeAll(); // Restore subscriptions
    };
    
    this.ws.onclose = (event) => {
      console.log('Connection closed:', event.code);
      if (!this.isReconnecting) {
        this.scheduleReconnect();
      }
    };
    
    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }
  
  scheduleReconnect() {
    if (this.retryCount >= this.maxRetries) {
      console.error('Max retry attempts reached. Giving up.');
      return;
    }
    
    this.isReconnecting = true;
    this.retryCount++;
    
    // Calculate delay with exponential backoff + jitter
    const delay = Math.min(
      this.baseDelay * Math.pow(2, this.retryCount - 1),
      this.maxDelay
    );
    
    // Add jitter to prevent thundering herd
    const jitteredDelay = delay + (Math.random() * 1000);
    
    console.log(`Reconnecting in ${jitteredDelay}ms (attempt ${this.retryCount}/${this.maxRetries})`);
    
    setTimeout(() => {
      this.connect();
    }, jitteredDelay);
  }
  
  subscribe(method, params, callback) {
    const id = this.generateId();
    this.subscriptions.set(id, { method, params, callback });
    
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.sendSubscription(id, method, params);
    }
    
    return id;
  }
  
  resubscribeAll() {
    console.log(`Restoring ${this.subscriptions.size} subscriptions`);
    for (const [id, sub] of this.subscriptions) {
      this.sendSubscription(id, sub.method, sub.params);
    }
  }
  
  sendSubscription(id, method, params) {
    this.ws.send(JSON.stringify({
      jsonrpc: '2.0',
      id: id,
      method: method,
      params: params
    }));
  }
  
  generateId() {
    return Date.now() + Math.random();
  }
}

测试重连逻辑

// Test disconnection scenarios
class NetworkSimulator {
  constructor(wsManager) {
    this.wsManager = wsManager;
  }
  
  // Simulate network outage
  simulateNetworkOutage(duration = 5000) {
    console.log('Simulating network outage...');
    
    // Force close the connection
    if (this.wsManager.ws) {
      this.wsManager.ws.close(1006, 'Network outage simulation');
    }
    
    // Block reconnection temporarily
    const originalConnect = this.wsManager.connect.bind(this.wsManager);
    this.wsManager.connect = () => {
      console.log('Connection blocked during outage simulation');
    };
    
    // Restore connection after duration
    setTimeout(() => {
      console.log('Network restored');
      this.wsManager.connect = originalConnect;
      this.wsManager.connect();
    }, duration);
  }
  
  // Simulate intermittent connectivity
  simulateIntermittentConnectivity() {
    setInterval(() => {
      if (Math.random() < 0.1) { // 10% chance every 10 seconds
        console.log('Simulating connection drop...');
        this.wsManager.ws?.close(1006, 'Intermittent connectivity');
      }
    }, 10000);
  }
}

// Usage
const simulator = new NetworkSimulator(wsManager);
simulator.simulateNetworkOutage(10000); // 10 second outage
生产环境关键:在生产应用中实现适当的重连逻辑是必不可少的。WebSocket 连接会断开——为此做好计划、测试,并在生产中监控。

故障排除

从标准RPC迁移

如果您当前正在使用HTTP轮询,以下是迁移到WebSockets的方法:
// Old approach - HTTP polling
class HTTPAccountMonitor {
  constructor(connection, accountAddress) {
    this.connection = connection;
    this.accountAddress = accountAddress;
    this.interval = null;
    this.lastKnownBalance = null;
  }

  start() {
    this.interval = setInterval(async () => {
      try {
        const accountInfo = await this.connection.getAccountInfo(
          new PublicKey(this.accountAddress)
        );
        
        const currentBalance = accountInfo?.lamports || 0;
        
        if (this.lastKnownBalance !== currentBalance) {
          console.log(`Balance changed: ${currentBalance}`);
          this.lastKnownBalance = currentBalance;
        }
      } catch (error) {
        console.error('Failed to fetch account info:', error);
      }
    }, 2000); // Poll every 2 seconds
  }

  stop() {
    if (this.interval) {
      clearInterval(this.interval);
      this.interval = null;
    }
  }
}

// New approach - WebSocket subscription
class WebSocketAccountMonitor {
  constructor(wsManager, accountAddress) {
    this.wsManager = wsManager;
    this.accountAddress = accountAddress;
    this.subscriptionId = null;
  }

  start() {
    this.subscriptionId = this.wsManager.subscribe(
      'accountSubscribe',
      [
        this.accountAddress,
        { encoding: 'jsonParsed', commitment: 'confirmed' }
      ],
      (data) => {
        const currentBalance = data.value.lamports;
        console.log(`Balance changed: ${currentBalance}`);
        
        // Handle the change immediately - no polling delay!
      }
    );
  }

  stop() {
    if (this.subscriptionId) {
      this.wsManager.unsubscribe(this.subscriptionId);
      this.subscriptionId = null;
    }
  }
}

增强功能

对于需要更高级功能的应用程序,请考虑使用LaserStream gRPC

历史重播

当您的应用程序离线时,补上错过的数据

多节点聚合

通过来自多个验证节点的数据增强可靠性

更高吞吐量

处理更多的订阅和更高的消息速率

企业功能

高级监控、分析和自定义数据管道
准备好开始了吗? 查看我们的WebSocket快速入门指南,获取实用示例和分步实施指南。