WebSocket 连接维护:从基础实现到生产级解决方案

在传统的HTTP协议中,客户端必须主动发起请求才能获取数据。而实时应用场景如在线聊天、股票行情、多人协作等,需要服务端能主动推送数据。WebSocket协议它具有:

  1. ​双向通信​​:客户端和服务端可以同时发送消息
  2. ​低延迟​​:建立连接后消息即时传输
  3. ​高效​​:相比HTTP轮询节省大量带宽

这是一个原生的websocket封装实现主要用于了解底层的实现,实际生产中推荐用 Socket.IO、SockJS这种第三方插件去做。自己手写原生有开发成本,兼容性,​​维护成本高等缺点。

一、技术架构概述

要实现一个完整的websocket,需要一个websocket实例,链接方法,监听消息,发送消息,断开连接,心跳维护链接。这里主要讲前端的封装,后端可以用express实现一个简单的node服务。后端的方法和前端一样也是有链接,消息监听,发送消息,关闭连接,在加上后端的定时推送。

完整的 WebSocket 实现需要包含以下核心模块:

  • WebSocket 实例管理
  • 连接建立与状态维护
  • 双向消息通信机制
  • 心跳保活策略
  • 异常恢复机制

本文主要聚焦前端实现,后端示例采用 Node.js + Express 构建。值得注意的是,生产环境的后端实现还需要考虑集群部署、消息广播等进阶功能。

二、前端基础实现

WebSocket 是浏览器原生支持的通信协议,以下是一个功能完备的基础实现:

<!DOCTYPE html>
<html>

<head>
    <title>WebSocket 通信演示</title>
    <style>
        #output {
            margin-top: 20px;
            border: 1px solid #eee;
            padding: 10px;
            min-height: 100px;
        }
    </style>
</head>

<body>
    <button onclick="connect()">建立连接</button>
    <button onclick="sendMessage()">发送消息</button>
    <div id="output"></div>

    <script>
        // WebSocket 实例与状态管理
        let socket;
        let heartbeatTimer;
        const HEARTBEAT_INTERVAL = 30000; // 心跳间隔30秒

        // 建立WebSocket连接
        function connect() {
            socket = new WebSocket('ws://localhost:3000');

            // 连接成功回调
            socket.onopen = () => {
                log('连接已建立');
                startHeartbeat();
            };

            // 消息接收处理
            socket.onmessage = (event) => {
                const data = parseMessage(event.data);
                if (data.type !== 'heartbeat') {
                    log(`收到消息: ${event.data}`);
                }
            };

            // 连接关闭处理
            socket.onclose = () => {
                clearInterval(heartbeatTimer);
                log('连接已断开');
                // 可在此处添加自动重连逻辑
            };

            // 错误处理
            socket.onerror = (error) => {
                log(`连接错误: ${error.message}`);
            };
        }

        // 消息发送封装
        function sendMessage() {
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                return alert('请先建立连接');
            }

            const message = prompt('请输入消息内容');
            if (message) {
                socket.send(message);
                log(`已发送: ${message}`);
            }
        }

        // 心跳保活机制
        function startHeartbeat() {
            clearInterval(heartbeatTimer); // 清除旧定时器

            heartbeatTimer = setInterval(() => {
                if (socket.readyState === WebSocket.OPEN) {
                    const heartbeatMsg = JSON.stringify({
                        type: 'heartbeat',
                        timestamp: Date.now()
                    });
                    socket.send(heartbeatMsg);
                }
            }, HEARTBEAT_INTERVAL);
        }

        // 消息解析辅助函数
        function parseMessage(data) {
            try {
                return JSON.parse(data);
            } catch {
                return { type: 'text', content: data };
            }
        }

        // 日志输出
        function log(message) {
            const output = document.getElementById('output');
            output.innerHTML += `<p>${new Date().toLocaleTimeString()}: ${message}</p>`;
            output.scrollTop = output.scrollHeight;
        }
    </script>
</body>

</html>

关键实现说明:

  1. ​心跳机制​​:通过定时发送特殊格式的心跳包,保持连接活跃并检测连接状态
  2. ​状态管理​​:通过readyState判断连接状态,避免在不可用状态下操作
  3. ​错误处理​​:统一处理连接错误和异常情况
  4. ​消息分类​​:通过消息类型字段区分心跳包和业务消息

二、初步封装

核心模块封装

  1. 连接管理机制
  • 自动重连策略:实现指数退避重连算法,上限5次重连尝试,最大间隔10秒
  • 连接状态隔离:通过闭包封装WebSocket实例,防止外部状态污染
  • 统一连接入口:对外暴露connect方法支持DOM元素绑定和自动重连
  1. 消息保障体系
  • 消息缓冲队列:在连接未就绪时暂存消息(pendingMessages)
  • 消息可靠传输:通过flushPendingMessages方法确保连接建立后顺序发送缓冲消息
  • 传输状态检测:实时校验readyState状态机,保证消息投递有效性
  1. 连接健康监测
  • 心跳检测机制:每30秒发送心跳包维持长连接
  • 异常断开处理:自动清除心跳定时器防止内存泄漏
  • 连接状态同步:通过onopen/onclose事件维护重连计数器
  1. 数据渲染层
  • 响应式数据更新:采用统一的数据接收处理器(onmessage)
  • 数据格式化输出:使用JSON.stringify进行数据美化展示
  • DOM智能更新:通过elment引用缓存实现精准内容更新
export default function ws() {
    let useWebsocket; //
    const receivedata = {} //
    let elment; // 存储 DOM 元素
    let reconnectAttempts = 0; // 重连次数
    const MAX_RECONNECTS = 5; //
    const pendingMessages = []; // 暂存消息的队列
    function connect(el) {
        if (el) elment = el;
        if (useWebsocket) {
            console.warn('WebSocket 已存在,请勿重复连接');
            return;
        } else {
            useWebsocket = new WebSocket('ws://localhost:3000');
        }

        useWebsocket.onopen = () => {
            console.log('连接成功');
            reconnectAttempts = 0;
            // 连接成功后发送所有暂存消息
            flushPendingMessages();
            startHeartbeat();
        };

        useWebsocket.onmessage = (event) => {
            console.log('收到消息: ' + event.data);
            const data = JSON.parse(event.data)
            receivedata[data.type] = data.data
            console.log('receivedata--', receivedata);
            changeInnerhtml(receivedata); // 直接渲染最新数据
        };

        useWebsocket.onclose = () => {
            console.log('连接关闭');
            if (reconnectAttempts < MAX_RECONNECTS) {
                const delay = Math.min(1000 * (reconnectAttempts + 1), 10000);
                console.log(`连接断开,${delay}ms后尝试重连...`);

                setTimeout(() => {
                    reconnectAttempts++;
                    connect();
                }, delay);
            } else {
                console.log('达到最大重连次数,停止尝试');
            }
        };
    }

    // 心跳配置
    const HEARTBEAT_INTERVAL = 30000; // 30秒
    let heartbeatTimer;
    function startHeartbeat() {
        heartbeatTimer = setInterval(() => {
            if (useWebsocket.readyState === WebSocket.OPEN) {
                useWebsocket.send(JSON.stringify({
                    type: 'heartbeat',
                    timestamp: Date.now()
                }));
            }
        }, HEARTBEAT_INTERVAL);
    }

    //发送消息
    /*    function sendMessage(message) {
           if (useWebsocket.readyState === WebSocket.OPEN) {
               useWebsocket.send(JSON.stringify({
                   type: 'message',
                   data: message
               }));
           } else {
               log('WebSocket 连接未建立,无法发送消息');
           }
       } */

    // 发送所有暂存消息
    const flushPendingMessages = () => {
        if (useWebsocket.readyState !== WebSocket.OPEN) return;

        while (pendingMessages.length > 0) {
            const message = pendingMessages.shift();
            if (message) {
                console.log('延迟发送的消息---', message);
                useWebsocket.send(message);
            }
        }
    };

    // 发送消息(核心改进)
    const sendMessage = (message) => {
        console.log('准备发送消息---', message);
        // 统一通过readyState判断
        if (useWebsocket.readyState === WebSocket.OPEN) {
            console.log('立即发送消息---', message);
            useWebsocket.send(message);
        } else {
            console.log('消息进入队列---', message);
            pendingMessages.push(message);
        }
    };
    //关闭连接
    function closeConnection() {
        if (useWebsocket) {
            useWebsocket.close();
            useWebsocket = null;
        }
    }
    //在页面输出结果   
    function changeInnerhtml(data) {
        if (!elment) return;
        const text = JSON.stringify(data, null, 2); // 格式化
        elment.innerHTML += `<p>${text}</p>`;
    }
    return { connect, sendMessage, closeConnection, receivedata }
}

自动连接使用实例:

<!DOCTYPE html>
<html>

<head>
    <title>WebSocket 测试</title>
</head>

<body>
    <button id="connect-btn">连接</button>
    <button id="send-btn">发送消息</button>
    <div id="output"></div>


    <script type="module">
        //自动连接
        import ws from './websocket.js';
        const { connect, sendMessage } = ws();

        const connectBtn = document.querySelector('#connect-btn');
        const sendBtn = document.querySelector('#send-btn');
        const output = document.querySelector('#output');
        connect(output);
        sendMessage('Hello, World!');
        sendMessage('发送消息');
    </script>
</body>

</html>

手动连接点击事件:

<!DOCTYPE html>
<html>

<head>
    <title>WebSocket 测试</title>
</head>

<body>
    <button id="connect-btn">连接</button>
    <button id="send-btn">发送消息</button>
    <div id="output"></div>


    <script type="module">
        import ws from './websocket.js';
        const websocket = ws();  // 先调用函数初始化

        const connectBtn = document.querySelector('#connect-btn');
        const sendBtn = document.querySelector('#send-btn');
        const output = document.querySelector('#output');
        //建立连接
        connectBtn.addEventListener('click', () => {
            websocket.connect(output);
        });
        //发送消息
        sendBtn.addEventListener('click', () => {
            const message = prompt('输入要发送的消息');
            if (message) websocket.sendMessage(message);
        });
    </script>
</body>

</html>

三、服务器架构设计

本方案采用 Express + ws 模块构建 WebSocket 服务器,具有以下特点:

  • ​双协议支持​​:同时支持 HTTP 和 WebSocket 协议
  • ​连接管理​​:实时跟踪客户端连接状态
  • ​消息广播​​:支持向所有客户端推送消息
  • ​类型化消息​​:结构化数据格式,便于前端处理
  • ​定时推送​​:服务端主动推送机制
const express = require('express');
const WebSocket = require('ws');

// 创建 Express 应用
const app = express();

// 直接使用 app.listen() 启动服务器并监听端口
const PORT = 3000;
const server = app.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}`);
    console.log(`WebSocket 运行在 ws://localhost:${PORT}`);
});


// 创建 WebSocket 服务器,附加到 HTTP 服务器上
const wss = new WebSocket.Server({ server });

// 存储所有连接的客户端
const clients = new Set();

// WebSocket 连接处理
wss.on('connection', (ws) => {
    console.log('新的客户端连接');

    // 将新客户端添加到集合中
    clients.add(ws);

    // 当收到客户端消息时
    ws.on('message', (message) => {
        console.log(`收到消息: ${message}`);
        console.log('发送消息给所有客户端clients---', clients);
        // 广播消息给所有客户端
        clients.forEach(client => {
            console.log(client === ws, client.readyState === WebSocket.OPEN);

            if (client.readyState === WebSocket.OPEN) {
                client.send(`服务器转发: ${message}`);
            }
        });
    });

    // 当客户端断开连接时
    ws.on('close', () => {
        console.log('客户端断开连接');
        clients.delete(ws);
    });

    // 1. 推送欢迎消息
    ws.send(JSON.stringify({
        type: 'welcome',
        data: '连接服务器成功',
        timestamp: Date.now()
    }));

    // 2. 推送系统状态
    ws.send(JSON.stringify({
        type: 'system_status',
        data: { cpu: 45, memory: 80 },
        timestamp: Date.now()
    }));

    // 3. 定时推送通知
    setInterval(() => {
        ws.send(JSON.stringify({
            type: 'notification',
            data: '您有1条新消息',
            timestamp: Date.now()
        }));
    }, 5000);
});

// Express 路由
app.get('/', (req, res) => {
    res.send(`
    <h1>WebSocket 服务器</h1>
    <p>打开浏览器控制台并运行以下代码测试:</p>
    <pre>
const ws = new WebSocket('ws://localhost:3000');
ws.onmessage = (event) => console.log('收到消息:', event.data);
ws.onopen = () => ws.send('你好,服务器!');
    </pre>
    <p>当前连接数: ${clients.size}</p>
  `);
});

这个实现提供了完整的 WebSocket 服务器解决方案,包括连接管理、消息处理、错误处理和性能监控等功能,可直接用于和前面前端代码进行联调。

posted @ 2025-04-04 18:01  雪旭  阅读(177)  评论(0)    收藏  举报