You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用setTimeout、setInterval与回调实现客户端心跳检测求助

搞定RecordTracker类的ping/pong客户端追踪逻辑

嘿,我来帮你一步步完善这个RecordTracker类,顺便解决你纠结的模拟客户端问题!你的核心需求很清晰:每隔10秒给客户端发ping,3秒没收到pong就踢掉,咱们拆解开来实现。

核心思路梳理

首先得明确几个关键步骤:

  • 每10秒遍历所有客户端,发送ping消息
  • 给每个发送了ping的客户端设置3秒超时器
  • 监听客户端的pong回复,收到后取消对应超时器
  • 超时未回复的客户端,从recordMap中移除
  • 需要一个临时存储来跟踪等待pong的客户端(避免重复处理或误删)

完善RecordTracker类

我们需要给类加一个状态来跟踪pending的pong请求,同时完善initialize里的逻辑:

interface Client { 
  sendMessage: (message: string) => void; 
  onMessage: (callback: (message: string) => void) => void; 
}

type RecordMap = { [recordName: string]: Client };

class RecordTracker {
  // 用来存储等待pong的客户端名和对应的超时ID
  private pendingPongs: Map<string, NodeJS.Timeout> = new Map();

  constructor(private recordMap: RecordMap) {}

  public initialize(): void {
    // 每10秒执行一轮检测
    setInterval(() => {
      // 遍历当前所有客户端
      Object.entries(this.recordMap).forEach(([clientName, client]) => {
        // 发送ping消息
        client.sendMessage('ping');

        // 设置3秒超时:如果没收到pong就移除客户端
        const timeoutId = setTimeout(() => {
          if (this.pendingPongs.has(clientName)) {
            // 移除未回复的客户端
            delete this.recordMap[clientName];
            this.pendingPongs.delete(clientName);
            console.log(`客户端 ${clientName} 未回复pong,已移除`);
          }
        }, 3000);

        // 把超时ID存入pendingPongs
        this.pendingPongs.set(clientName, timeoutId);

        // 监听客户端的pong回复
        client.onMessage((message) => {
          if (message === 'pong' && this.pendingPongs.has(clientName)) {
            // 收到pong,清除超时器
            clearTimeout(this.pendingPongs.get(clientName)!);
            this.pendingPongs.delete(clientName);
            console.log(`客户端 ${clientName} 已回复pong`);
          }
        });
      });
    }, 10000);
  }

  public getRecordMap(): RecordMap {
    return { ...this.recordMap };
  }
}

为什么需要模拟客户端?

当然需要!没有模拟客户端的话,你没法验证代码是否真的能发送ping、监听pong、超时移除这些逻辑。咱们写一个MockClient来模拟真实的客户端行为:

class MockClient implements Client {
  private messageCallbacks: ((message: string) => void)[] = [];
  public sentMessages: string[] = []; // 用来记录发送的消息,方便测试

  sendMessage(message: string): void {
    this.sentMessages.push(message);
    console.log(`Mock客户端发送消息:${message}`);
  }

  onMessage(callback: (message: string) => void): void {
    this.messageCallbacks.push(callback);
  }

  // 手动触发pong回复,模拟客户端收到ping后回复
  triggerPong(): void {
    this.messageCallbacks.forEach(cb => cb('pong'));
  }
}

测试你的逻辑

现在可以用MockClient来测试RecordTracker的功能了:

// 创建两个模拟客户端
const client1 = new MockClient();
const client2 = new MockClient();

// 初始化RecordTracker
const tracker = new RecordTracker({
  clientA: client1,
  clientB: client2
});

// 启动追踪
tracker.initialize();

// 模拟client1回复pong,client2不回复
setTimeout(() => {
  client1.triggerPong();
}, 2000);

// 15秒后查看剩余客户端(此时已经过了一轮10秒检测+3秒超时)
setTimeout(() => {
  console.log('剩余客户端:', Object.keys(tracker.getRecordMap()));
  // 应该只剩下clientA
}, 15000);

一些注意事项

  • 内存泄漏问题:如果客户端频繁加入或移除,要确保pendingPongs里的超时器和回调都被正确清理,避免内存泄漏。
  • 重复监听问题:上面的代码每次轮询都会给客户端绑定onMessage回调,可能会导致同一个客户端有多个回调。可以优化成给每个客户端只绑定一次回调,或者在轮询前先清除旧的回调(取决于你的业务场景)。
  • 线程安全:如果是在多线程环境下,可能需要加锁,但Node.js是单线程的,所以暂时不用考虑。

这样你的RecordTracker就完全实现需求啦,模拟客户端也能帮你验证所有逻辑是否正常工作!

内容的提问来源于stack exchange,提问作者bootsElectric

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:40:56