websocket后端实现心跳检测,并定时清理异常的连接

要实现WebSocket心跳检测和异常连接清理 ,核心思想是记录每个连接的最后活动时间 (Last Seen Timestamp) ,并设置一个定时任务 (Timer/Scheduler) 来定期检查这些时间戳是否超出了预设的容忍范围(Timeout)。


1. 核心原理与策略

A. 心跳机制 (Heartbeat Mechanism)

心跳可以有两种实现方式:

  1. 客户端主动 Ping/Pong (推荐):

    • 服务器每隔 NNN 秒向所有连接发送一个 PING 消息。
    • 客户端必须在 TTT 秒内回复一个 PONG
    • 如果服务器在 TTT 秒后没有收到 PONG,则认为该连接超时或中断,触发清理。
  2. 服务端状态跟踪 (Timeout):

    • 服务器不依赖客户端的明确心跳,而是基于实际消息的接收时间来判断连接是否"活跃"。
    • 当服务器接收到任何数据时,立即更新该连接的 last_activity 标记。
    • 如果在设定的最大空闲时间 TidleT_{idle}Tidle 内没有收到任何活动,则视为异常。

B. 清理逻辑 (Cleanup Logic)

主要采用服务端状态跟踪 (Timeout) 的策略来实现定时清理。

  1. 数据结构 : 需要一个映射表来存储所有活跃的连接及其状态。
    Connections={Connection ID:{Socket Object,Last Activity Timestamp}}\text{Connections} = \{ \text{Connection ID} : \{ \text{Socket Object}, \text{Last Activity Timestamp} \} \}Connections={Connection ID:{Socket Object,Last Activity Timestamp}}

  2. 定时任务 (The Cleaner) : 启动一个后台线程或使用框架提供的定时调度器,每隔 Δt\Delta tΔt 秒执行一次检查。

    • 在每次检查中,遍历 Connections\text{Connections}Connections 中的所有条目。
    • 对于每个连接,计算 CurrentTime−Last Activity Timestamp\text{CurrentTime} - \text{Last Activity Timestamp}CurrentTime−Last Activity Timestamp。
    • 如果结果 ≥Tidle\ge T_{idle}≥Tidle(空闲时间超过阈值),则执行清理操作:
      a. 关闭对应的 Socket 连接。
      b. 从 Connections\text{Connections}Connections 映射中移除该连接记录。

2. 示例架构 (以 Node.js/WebSocket 为例)

假设我们使用 Node.js 的 ws 库作为后端实现。

步骤一:连接管理类设计

我们需要一个类来集中管理所有连接的状态和清理逻辑。

javascript 复制代码
class ConnectionManager {
    constructor(idleTimeoutMs = 300000) { // 默认空闲超时时间:5分钟
        // 存储所有连接状态: { socket: wsInstance, lastActivity: timestamp }
        this.connections = new Map();
        this.IDLE_TIMEOUT = idleTimeoutMs;
    }

    addConnection(socket) {
        const connectionId = Date.now() + Math.random().toString(36).substring(2, 9);
        this.connections.set(connectionId, { 
            socket: socket, 
            lastActivity: Date.now() 
        });
        console.log(`Connection added with ID: ${connectionId}`);
    }

    updateActivity(connectionId) {
        if (this.connections.has(connectionId)) {
            this.connections.get(connectionId).lastActivity = Date.now();
        }
    }

    removeConnection(connectionId) {
        const data = this.connections.get(connectionId);
        if (data && data.socket) {
            console.log(`[Cleanup] Closing connection ID: ${connectionId}`);
            // 关键步骤:关闭Socket
            data.socket.end(); 
        }
        this.connections.delete(connectionId);
    }

    /**
     * 定时清理函数 (Heartbeat Cleaner)
     */
    cleanupConnections() {
        const now = Date.now();
        console.log(`\n--- Running Heartbeat Check at ${new Date().toISOString()} ---`);
        
        for (const [id, data] of this.connections.entries()) {
            const idleTime = now - data.lastActivity;

            if (idleTime > this.IDLE_TIMEOUT) {
                console.warn(`[Cleanup Triggered] Connection ID ${id} timed out. Idle for: ${(idleTime / 1000).toFixed(1)}s`);
                this.removeConnection(id);
            }
        }
        console.log(`Total active connections: ${this.connections.size}`);
    }
}

module.exports = ConnectionManager;

步骤二:集成到 WebSocket 服务器

在主服务器中,每次消息接收后 调用 updateActivity。同时,你需要启动一个定时器 来周期性地调用 cleanupConnections

javascript 复制代码
const WebSocket = require('ws');
const ConnectionManager = require('./ConnectionManager');

// 1. 初始化管理器 (设置超时时间为1分钟)
const manager = new ConnectionManager(60000); // 60秒空闲超时

// 2. 创建WebSocket Server
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
    console.log('Client connected.');
    // 当新连接建立时,添加到管理器
    manager.addConnection(ws);

    // --- 监听来自客户端的消息 (更新活动时间) ---
    ws.on('message', function incoming(message) {
        // 接收到任何消息,立即更新该连接的最后活动时间
        const connectionId = Object.keys(manager.connections).find(key => manager.connections.get(key).socket === ws);
        if (connectionId) {
             manager.updateActivity(connectionId);
        }
    });

    ws.on('close', () => {
        // 客户端主动关闭连接时,立即清理记录
        const connectionId = Object.keys(manager.connections).find(key => manager.connections.get(key).socket === ws);
        if (connectionId) {
            manager.removeConnection(connectionId);
        }
    });
});

// 3. 启动定时清理任务 (例如,每10秒检查一次)
const CLEANUP_INTERVAL = 10000; // 10秒
setInterval(() => {
    manager.cleanupConnections();
}, CLEANUP_INTERVAL);

console.log('WebSocket Server started and Heartbeat monitoring enabled.');

3. 总结与注意事项

环节 目的 实现方式 注意事项
状态记录 跟踪连接的"新鲜度" 使用 Map 或对象存储 lastActivity 时间戳。 确保 ID 的唯一性和高效的查找。
活动更新 标记连接是活跃的 任何消息(on('message'))到达时,立即更新该记录的时间戳。 这是防止误判超时的关键。
定时清理 发现和移除死连接 使用 setInterval 或异步调度器定期遍历所有记录。 定时间隔 (Δt\Delta tΔt) 需要平衡检测频率和资源消耗。
异常处理 连接关闭 cleanupConnections 中,使用 socket.end() 来强制关闭流。 确保在尝试操作已关闭的资源时不会抛出错误。

通过这种结合了实时活动跟踪后台定时检查的机制,WebSocket后端就能有效地检测到那些长时间没有交互的"僵尸"连接,并及时将其清理掉,从而释放服务器资源,保证系统的健康运行。

相关推荐
阿米亚波1 小时前
【EVE-NG 实战】防火墙基础(VLAN · VRRP · NAT · ACL · 静态路由)
运维·网络·笔记·网络协议·计算机网络·网络安全·运维开发
tiantianuser2 小时前
NVME-oF IP 设计2 :设计之前的调研!
网络·网络协议·rdma·roce v2·nvme of
2401_873479403 小时前
如何识别代理IP和伪装流量?用IP情报工具三步穿透住宅代理伪装
网络·数据库·网络协议·tcp/ip·ip
STDD3 小时前
Traefik 中间件深度配置:限流、认证、IP 白名单与错误页面定制
网络协议·tcp/ip·中间件
脉动数据行情3 小时前
Python WebSocket 实时订阅 NQ(小型纳斯达克指数)完整监听程序
websocket·小纳指·纳斯达克·国际股指·python 量化盯盘
console.log('npc')4 小时前
AI Agent 前端流式渲染核心技术文档(SSE \+ WebSocket \+ 打字机渲染)
前端·人工智能·websocket
曾令胜4 小时前
HTTP协议基础总结
网络·网络协议·http
sdghterhd14 小时前
WebSocket 快速入门教程(附示例源码)
网络·websocket·网络协议
IpdataCloud20 小时前
AI生成的Avalon恶意框架怎么防?用IP离线库识别模块化C2通信
运维·人工智能·网络协议·tcp/ip·ip
一口一口吃成大V1 天前
lattice mipi ip开启DESKEW_EN属性(“ENABLED“),输出异常
网络·网络协议·tcp/ip