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后端就能有效地检测到那些长时间没有交互的"僵尸"连接,并及时将其清理掉,从而释放服务器资源,保证系统的健康运行。

相关推荐
达不溜的日记5 小时前
CAN总线网络传输层CanTp详解
网络·stm32·嵌入式硬件·网络协议·网络安全·信息与通信·信号处理
wanhengidc5 小时前
网站服务器具体功能有哪些?
运维·服务器·网络·网络协议·智能手机
SCBAiotAigc5 小时前
2026.4.13:vim编程简单配置
人工智能·ubuntu·vim·具身智能
杨凯凡6 小时前
【006】常见 WebSocket 场景与后端 session/鉴权的关系
网络·websocket·网络协议
西西弟7 小时前
网络编程基础之TCP循环服务器
运维·服务器·网络·网络协议·tcp/ip
0xDevNull9 小时前
Spring Boot 3.x WebSocket 实战教程
spring boot·后端·websocket
呆呆在发呆.9 小时前
JavaEE初阶
java·jvm·网络协议·学习·udp·java-ee·tcp
北京耐用通信9 小时前
自动化行业异构集成实践:耐达讯自动化实现CAN转EtherCAT高效互操作
人工智能·科技·网络协议·自动化·信息与通信
上海云盾安全满满9 小时前
http与https有什么区别,https攻击要如何防护
网络协议·http·https