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

相关推荐
wangl_921 小时前
Modbus RTU 与 Modbus TCP 深入指南-总览对比
网络·网络协议·tcp/ip·tcp·modbus·rtu
wangl_921 小时前
Modbus RTU 与 Modbus TCP 深入指南-CRC校验完全解析
网络·网络协议·tcp/ip·tcp·modbus·rtu
郝学胜-神的一滴3 小时前
Python 鸭子类型:优雅的多态哲学,让代码更自由
linux·服务器·开发语言·python·网络协议
qq_5895681011 小时前
springbootweb案例,出现访问 http://localhost:8080/list 一直处于浏览器运转阶段
java·网络协议·http·list·springboot
凯瑟琳.奥古斯特18 小时前
DNS解析全流程详解
网络·网络协议
TickDB19 小时前
Python 接入国内期货 Tick 行情:从 CTP 到统一 API 的工程实践
python·websocket
Deitymoon19 小时前
ESP8266——TCP客户端
网络·网络协议·tcp/ip
Deitymoon1 天前
ESP8266——透传
单片机·网络协议·tcp/ip
Deitymoon1 天前
ESP8266——UDP传输
单片机·网络协议·udp