WebSocket技术与心跳检测

WebSocket基础方法

示例中前端使用技术为React 后端使用Nodejs Express框架

后端

在node中我们先提前引入ws.Server模块(需要提前安装ws,npm i ws) 然后注册服务 注意这里有一个端口属性 最好设置成一个服务器中未被使用的端口

js 复制代码
const server=new WebSocket({ port: 8080 },()=>{
  console.log('启动服务')
})

在注册好的服务对象中有一个on方法 可以执行这个方法启动服务,on方法中有两个参数,一个是链接类型,一个是建立链接后的回调函数

js 复制代码
// 建立链接
server.on("connection",connectHandle)

在回调函数中包含了服务链接中会出现的各种事件: 服务关闭事件close 服务错误事件error 信息交互事件message. 其中ws参数中包含on方法 用来挂载处理这些事件的回调函数

不同的回调函数具备不用的作用,其中最重要的是message事件的回调函数,这个函数中存在data属性可以接受客户端发送的信息,也可以向客户端主动发送信息

js 复制代码
const connectHandle=(ws)=>{
  console.log("客户端链接")
  // 监听客户端出错
  ws.on('error',errHandler)
  // 监听客户端断开链接
  ws.on("close",closeHandler)
  // 监听客户端发送的消息
  ws.on('message',messageHandler)
}
// 链接执行的回调函数
const errHandler=(error)=>{
  console.log("监听到错误",error);
}
const closeHandler=()=>{
  console.log("客户端链接关闭");
}
// 该回调函数可以接受客户端发送的消息 也可以主动向客户端发送
function messageHandler(data){
  console.log("客户端发送信息",JSON.parse(data));
  // 服务端可以在message的回调函数中向客户端传递消息
  this.send(JSON.parse(data))
}

完整代码

js 复制代码
// 引入ws模块
const WebSocket =require('ws').Server
// 创建服务
const server=new WebSocket({ port: 8080 },()=>{
  console.log('启动服务')
})
// 链接执行的回调函数
const errHandler=(error)=>{
  console.log("监听到错误",error);
}
const closeHandler=()=>{
  console.log("客户端链接关闭");
}
// 该回调函数可以接受客户端发送的消息 也可以主动向客户端发送
function messageHandler(data){
  console.log("客户端发送信息",JSON.parse(data));
  // 服务端可以在message的回调函数中向客户端传递消息
  this.send(JSON.parse(data))
}
const connectHandle=(ws)=>{
  console.log("客户端链接")
  // 监听客户端出错
  ws.on('error',errHandler)
  // 监听客户端断开链接
  ws.on("close",closeHandler)
  // 监听客户端发送的消息
  ws.on('message',messageHandler)
}
// 建立链接
server.on("connection",connectHandle)
module.exports = router;

前端

前端中直接实例化WebSocket对象 参数是服务端地址+自定义的端口(注意: 这里的协议不是http协议,而是ws协议,所以地址开头是ws://).

前端和后端类似,需要自定定义三个事件的回调函数,open事件 message事件,err事件. 在这些事件中可以通ws对象中的send方法向后端发送信息

tsx 复制代码
// 实例化ws对象
const ws=new WebSocket("ws://localhost:8080")
// open事件
ws.onopen=function(){
// 客户端可以使用send方法向服务端发送信息
ws.send('111123456')
console.log("链接成功");
}
// message事件
ws.onmessage=function(e){
// 接受服务端发送给客户端的消息
console.log("接受到消息",e.data);
}
// close事件
ws.onclose=function(){
console.log("链接关闭");
}
// error事件
ws.onerror=function(){
console.log("链接错误");
}

WebSocket设置心跳检测机制

在 WebSocket 连接中,客户端和服务端都可能出现意外断连的情况。心跳检测机制(Heartbeat Mechanism)旨在确保连接的有效性。其工作原理是:客户端定时向服务端发送特定的"心跳包",服务端收到后立即响应。若客户端收到响应,则证明连接正常;若在规定时间内未收到响应,则判定连接已断开,并触发断线重连流程。通常重连操作由定时器控制,且重连的尝试间隔应大于心跳包的发送间隔,以避免频繁无效请求。

前端部分

在前端开发中我们通常将这一复杂的操作直接封装成一个类方便后续使用

  1. 变量与构造函数
    分别是服务器的地址,消息类型,ws:webSocket对象,webSocket链接状态,
    心跳检测相应参数: 心跳检测间隔时间,断线重连间隔时间,相应超时间隔时间;触发断线重连的定时器
ts 复制代码
wsUrl:string;
// 消息类型,如果是普通消息直接处理 就原封不动发送给后端
ModeCode={
    MSG:'message',
    Heart_Beat:'heart_beat'
}
ws:WebSocket|null=null; // webSocket对象
webSocketState=false// websocket链接状态
heartBeat={
  time:5000,//心跳检测时间
  reconnect:10000, //断线重连时间
  timeOut:3000,//心跳超时间隔时间
}
reconnectMc:any=null//断线重连时间器
constructor(wsUrl:string){
  this.wsUrl=wsUrl;
}
  1. 各函数及其作用
    链接服务函数: connectwebsocket(): 用于创建webSocket对象并初始化服务
ts 复制代码
  connectWebSocket(){
      this.ws=new WebSocket(this.wsUrl);
      this.init()
  }

初始化函数: init(): 用于初始化 websocket对象 并注册相应响应事件

  • open事件: 当检测到链接成功之后就触发该事件,会讲链接状态设置为true 并判断心跳检测信息是否存在 如果存在就触发心跳检测开始函数
  • message事件: 当检测到服务器端发送信息后触发函数, 将信息结构并判断事件类型,如果是普通事件就接受,如果是心跳检测事件就将当前链接状态设置为true表示链接正常,并将信息原封不动传递给后端
ts 复制代码
init(){//初始化函数
        this.ws?.addEventListener('open',()=>{
            console.log("已连接");
            // 设置ws状态为true
            this.webSocketState=true;
            if(this.heartBeat&&this.heartBeat.time){
                this.startHeartBeat(this.heartBeat.time)
            }
        })

        this.ws?.addEventListener('message',(e)=>{
            console.log(e.data)
            const data=JSON.parse(e.data)
            switch(data.ModeCode){
                case this.ModeCode.MSG:
                    console.log("收到普通消息,不需要返回前端");
                    break;
                case this.ModeCode.Heart_Beat:
                    console.log("心跳消息");
                    this.webSocketState=true
                    console.log(e.data)
                    break;
            }
        })
        this.ws?.addEventListener('close',(e)=>{
            this.webSocketState=false;
            console.log("断开链接");
        })
    }
  1. 心跳检测初始化函数 startHeartBeat: 用于启动心跳检测
    参数是心跳检测的间隔时间,启动定时器,在对应时间后向后端发送心跳检测信息,信息的类型是心跳检测, 然后触发waitingServer函数 等待服务器响应
ts 复制代码
  startHeartBeat(time:number){
      setTimeout(()=>{
          this.ws?.send(JSON.stringify({
              ModeCode:this.ModeCode.Heart_Beat,
              msg:new Date()
          }))
          this.waitingServer();
      },time)
  }
  1. 等待服务器响应函数 waitingServer
    触发该函数,先将链接状态设置为false,等待后端发送信息,(这段时间内如果后端发送了心跳检测信息就会执行message事件,并在该事件响应函数中将链接状态设置为true.如果响应失败那么链接状态仍为false),在设置好的timeout时间后触发判断,如果当前链接状态为true就重新触发心跳开始函数,进行新一轮的心跳检测.如果为false就说明链接断线了,需要将websocket链接关闭,并清空ws对象,然后执行重连操作
ts 复制代码
waitingServer(){
    this.webSocketState=false;
    setTimeout(()=>{
        if(this.webSocketState){
            this.startHeartBeat(this.heartBeat.time)
            return;
        }
        console.log("心跳无响应,已断线")
        try{
            this.ws?.close();
            this.ws=null;
        }catch(e){
            console.log("链接关闭",e)
        }
        this.reconnectWebSocket()
    },this.heartBeat.timeOut)
}
  1. 重连调度器: reconnectWebSocket
  • 它的作用是开启一个定时器( setTimeout ),在等待 this.heartBeat.reconnect (代码中定义为 10000ms,即 10 秒)之后,执行具体的重连逻辑 reconnectWs 。
  • 这种延迟机制是为了避免在网络波动或服务器宕机时,客户端频繁地发起无效连接请求(即"防抖"或减轻服务器压力)。
ts 复制代码
reconnectWebSocket(){
    this.reconnectMc=setTimeout(()=>{
        this.reconnectWs()
    },this.heartBeat.reconnect);
}
  1. 重新链接函数 reconnectWs
  • 初始化连接 : if(!this.ws) 判断当前是否没有 WebSocket 实例。如果没有,则调用 this.connectWebSocket() 发起新的连接。
  • 清理与防重复 : if(this.ws && this.reconnectMc) 这里的逻辑是为了处理"已有连接实例但仍在重连流程中"的情况。它会清除当前的重连定时器 reconnectMc ,置空引用,然后再次调用 reconnectWebSocket() 。
ts 复制代码
reconnectWs(){
    if(!this.ws){
        //第一次执行,初始化
        this.connectWebSocket()
    }
    if(this.ws && this.reconnectMc){
        clearInterval(this.reconnectMc)
        this.reconnectMc=null
        this.reconnectWebSocket();
    }
}
  1. 前端调用方法
ts 复制代码
const wsServer=new ws("ws://localhost:8080")
wsServer.connectWebSocket()

后端部分

后端部分相较于普通写法基本没有变化,只需要在处理message事件的时候判断一下前端传入的事件类型 如果是心跳检测信息,就原封不动的发送给前端,如果不是就是普通消息不需要额外处理

js 复制代码
function messageHandler(data){
  console.log("客户端发送信息",JSON.parse(data));
  // 服务端可以在message的回调函数中向客户端传递消息
  // this.send(JSON.parse(data))
  const {ModeCode}=JSON.parse(data)
  switch(ModeCode){
    case "message":
      console.log("收到普通消息,不需要返回前端");
      break;
    case"heart_beat":
      console.log("心跳检测");
      // 如果是心跳检测包就原封不动的返回前端
      this.send(JSON.stringify(JSON.parse(data)))
      break;
  }
}

完整代码展示

ws类定义代码

ts 复制代码
class ws{
    wsUrl:string;
    // 消息类型,如果是普通消息直接处理 就原封不动发送给后端
    ModeCode={
        MSG:'message',
        Heart_Beat:'heart_beat'
    }
    ws:WebSocket|null=null; 
    webSocketState=false// websocket链接状态
    heartBeat={
        time:5000,//心跳检测时间
        reconnect:10000, //断线重连时间
        timeOut:3000,//心跳超时间隔时间
    }
    reconnectMc:any=null//断线重连时间器
    constructor(wsUrl:string){
        this.wsUrl=wsUrl;
    }
    connectWebSocket(){
        this.ws=new WebSocket(this.wsUrl);
        this.init()
    }
    init(){//初始化函数
        this.ws?.addEventListener('open',()=>{
            console.log("已连接");
            // 设置ws状态为true
            this.webSocketState=true;
            if(this.heartBeat&&this.heartBeat.time){
                this.startHeartBeat(this.heartBeat.time)
            }
        })

        this.ws?.addEventListener('message',(e)=>{
            console.log(e.data)
            const data=JSON.parse(e.data)
            switch(data.ModeCode){
                case this.ModeCode.MSG:
                    console.log("收到普通消息,不需要返回前端");
                    break;
                case this.ModeCode.Heart_Beat:
                    console.log("心跳消息");
                    this.webSocketState=true
                    console.log(e.data)
                    break;
            }
        })
        this.ws?.addEventListener('close',(e)=>{
            this.webSocketState=false;
            console.log("断开链接");
        })
    }
    // 心跳检测初始化函数
    startHeartBeat(time:number){
        setTimeout(()=>{
            this.ws?.send(JSON.stringify({
                ModeCode:this.ModeCode.Heart_Beat,
                msg:new Date()
            }))
            this.waitingServer();
        },time)
    }
    // 延时等待服务端响应: 通过webSocketState判断是否连线成功
    waitingServer(){
        this.webSocketState=false;
        setTimeout(()=>{
            if(this.webSocketState){
                this.startHeartBeat(this.heartBeat.time)
                return;
            }
            console.log("心跳无响应,已断线")
            try{
                this.ws?.close();
                this.ws=null;
            }catch(e){
                console.log("链接关闭",e)
            }
            this.reconnectWebSocket()
        },this.heartBeat.timeOut)
    }
    // 重连操作
    reconnectWebSocket(){
        this.reconnectMc=setTimeout(()=>{
            this.reconnectWs()
        },this.heartBeat.reconnect);
    }
    reconnectWs(){
        if(!this.ws){
            //第一次执行,初始化
            this.connectWebSocket()
        }
        if(this.ws && this.reconnectMc){
            clearInterval(this.reconnectMc)
            this.reconnectMc=null
            this.reconnectWebSocket();
        }
    }
}
export default ws

ws类使用代码

tsx 复制代码
import ws from "./utils/websocket"

function App() {
  const wsServer=new ws("ws://localhost:8080")
  wsServer.connectWebSocket()
  return (
    <>
      1
    </>
  )
}
export default App

后端代码

js 复制代码
var express = require('express');
var router = express.Router();
// 引入ws模块
const WebSocket =require('ws').Server

// 创建服务
const server=new WebSocket({ port: 8080 },()=>{
  console.log('启动服务')
})

// 链接执行的回调函数
const errHandler=(error)=>{
  console.log("监听到错误",error);
}
const closeHandler=()=>{
  console.log("客户端链接关闭");
}
// 该回调函数可以接受客户端发送的消息 也可以主动向客户端发送
function messageHandler(data){
  console.log("客户端发送信息",JSON.parse(data));
  // 服务端可以在message的回调函数中向客户端传递消息
  // this.send(JSON.parse(data))
  const {ModeCode}=JSON.parse(data)
  switch(ModeCode){
    case "message":
      console.log("收到普通消息,不需要返回前端");
      break;
    case"heart_beat":
      console.log("心跳检测");
      // 如果是心跳检测包就原封不动的返回前端
      this.send(JSON.stringify(JSON.parse(data)))
      break;
  }
}
const connectHandle=(ws)=>{
  console.log("客户端链接")
  // 监听客户端出错
  ws.on('error',errHandler)
  // 监听客户端断开链接
  ws.on("close",closeHandler)
  // 监听客户端发送的消息
  ws.on('message',messageHandler)
}
// 建立链接
server.on("connection",connectHandle)
module.exports = router;
相关推荐
Coffeeee1 小时前
年过完了,该上班了,我用Compose给大家放个烟花喜庆喜庆
前端·kotlin·android jetpack
Marshall1511 小时前
UniApp 安卓端版本检查更新功能完整实现
前端
不会敲代码11 小时前
从零开始掌握LangChain工具调用:让AI拥有“动手能力”
前端·langchain
a1117761 小时前
波浪圆圈背景效果(html 开源)
前端·html
程序员ys1 小时前
网页白屏的原理与优化
前端·性能优化·浏览器
@PHARAOH2 小时前
WHAT - SWC Rust-based platform for the Web
开发语言·前端·rust
滕青山2 小时前
HTML编码/解码 核心JS实现
前端·javascript·vue.js