Fastify+TS实现基础IM服务(六)心跳机制

扩展ExtendedWebSocket

ts 复制代码
// 扩展WebSocket连接以包含房间和用户ID
export interface ExtendedWebSocket extends WebSocket {
  userId?: string // 可选属性,表示用户的唯一标识符。这对于追踪用户的行为和管理用户状态非常重要。
  room?: string // 可选属性,表示用户当前所在的房间ID。如果用户当前不在任何房间中,则此属性可以不存在或为 undefined。
  rooms?: Set<string> // 可选属性,一个字符串集合,包含用户加入的所有房间的ID。使用 Set 是因为它自动确保所有元素都是唯一的,避免了重复的房间ID。
  connectedAt?: Date // 可选属性,记录了 WebSocket 连接建立的时间。这可以用于计算连接持续时间或用于日志记录。
  lastHeartbeatSent?: Date // 可选属性,记录了最后一次从服务器发送心跳消息的时间。心跳机制通常用于维持连接的活性,防止因为超时而被自动关闭。
  lastHeartbeatReceived?: Date // 可选属性,记录了最后一次接收到客户端心跳的时间。这同样是为了监控连接的健康状况。
}

ExtendedWebSocket中新增的这几个属性就是我们今天的主题:心跳机制

在实时通信系统中,维持客户端与服务器之间的连接是至关重要的。WebSocket作为一种在单个TCP连接上提供全双工通信通道的技术,广泛应用于需要实时数据传输的场景,例如在线游戏、聊天应用和金融市场数据更新等。然而,WebSocket连接可能因为网络波动、客户端休眠、服务器重启等原因而中断。为了解决这些问题,心跳机制和连接状态的维护显得尤为重要。

心跳机制

心跳机制是一种通过定期发送小数据包来检测和维持连接活性的方法。在ExtendedWebSocket中,lastHeartbeatSentlastHeartbeatReceived属性分别记录了最后一次发送和接收心跳的时间。这些时间戳可以用来判断连接是否仍然活跃。

服务器端可以定期向客户端发送心跳消息,如果在一定时间内没有收到客户端的响应,则认为客户端已经断开连接,服务器可以主动关闭该连接。同理,客户端也可以定期发送心跳消息给服务器,以证明自己的活跃状态。

状态维护

在多用户实时交互应用中,跟踪用户的状态、所在房间等信息是非常必要的。ExtendedWebSocket通过引入userIdroomrooms属性来实现这一功能。

  • userId是用户的唯一标识符,用于区分不同的用户。
  • room表示用户当前所在的房间ID。这对于实现如聊天室这样的应用场景非常有用,服务器可以根据房间ID将消息广播给同一房间内的所有用户。
  • rooms是一个包含用户加入的所有房间ID的集合。这对于支持用户同时加入多个房间的应用场景非常重要。

connectedAt属性记录了WebSocket连接建立的时间,这对于分析用户行为、计算连接持续时间等有一定的用途。

心跳方案设计

在基于ExtendedWebSocket接口的实时通信系统中,设计一个有效的心跳方案是保持连接稳定性的关键。心跳机制不仅能够维持连接的活性,避免因超时而断开,还可以作为监测连接健康状况的手段。以下是一个心跳方案的设计思路,包括服务器端和客户端的实现策略。

服务器端心跳方案设计

服务器端的心跳方案主要负责定期向客户端发送心跳消息,并监测客户端对心跳消息的响应。

  1. 定期发送心跳消息:服务器应定期(例如每10秒)向所有已连接的客户端发送心跳消息。这可以通过设置一个定时器实现。
  2. 监测心跳响应 :服务器需要跟踪最后一次向客户端发送心跳消息的时间(lastHeartbeatSent)和最后一次收到客户端心跳响应的时间(lastHeartbeatReceived)。如果在设定的超时时间内(例如30秒)没有收到客户端的心跳响应,则认为该连接已经不活跃,服务器应主动关闭该连接。
  3. 处理客户端心跳响应 :当服务器收到客户端的心跳响应时,应更新lastHeartbeatReceived时间戳,以标记该连接仍然活跃。

客户端心跳方案设计

客户端的心跳方案主要负责响应服务器的心跳消息,并可主动向服务器发送心跳消息。

  1. 响应服务器心跳消息:客户端在收到服务器的心跳消息后,应立即回复一个心跳响应消息。这确保了服务器能够正确判断客户端的活跃状态。
  2. 主动发送心跳消息:除了响应服务器的心跳消息,客户端也可以主动定期(例如每10秒)发送心跳消息到服务器。这有助于客户端证明自己的活跃状态,尤其是在网络条件不稳定时。

服务器端心跳方案实现

ts 复制代码
// 心跳间隔时间设置为10秒
const HEARTBEAT_INTERVAL = 10000

/**
 * 定期向所有客户端发送心跳消息。
 */
const sendHeartbeat = () => {
  const now = new Date()
  const heartbeatMessage = JSON.stringify({
    type: 'heartbeat',
    message: 'ping'
  })

  Object.values(rooms).forEach((room) => {
    room.forEach((client) => {
      // 检查是否收到过心跳响应,并且自上次发送心跳以来是否已超过两倍心跳间隔时间
      if (
        client.lastHeartbeatReceived &&
        client.lastHeartbeatSent &&
        client.lastHeartbeatSent.getTime() -
          client.lastHeartbeatReceived.getTime() >
          HEARTBEAT_INTERVAL * 2
      ) {
        logger.warn(`${client.userId} is not ready. Close connection.`)
        // 关闭死连接
        client.close()
        // 从房间中移除
        room.delete(client)
      } else if (client.readyState === WebSocket.OPEN) {
        client.lastHeartbeatSent = now
        // 对于活跃的连接,发送心跳消息
        client.send(heartbeatMessage)
      }
    })
  })
}

// 使用 setInterval 设置定时任务,定期执行 sendHeartbeat 函数发送心跳
setInterval(sendHeartbeat, HEARTBEAT_INTERVAL)

// 处理客户端心跳响应
export const handleHeartbeat = (socket: ExtendedWebSocket) => {
  socket.lastHeartbeatReceived = new Date() // 更新收到心跳的时间
  logger.info(`Heartbeat received from ${socket.userId} in room ${socket.room}`)
}

这段代码展示了在基于WebSocket的实时通信系统中,如何实现心跳机制以维持连接的活性和监控连接的健康状态。心跳机制通过定期发送小数据包(心跳消息)来实现。代码分为两个主要部分:定期发送心跳消息的sendHeartbeat函数和处理客户端心跳响应的handleHeartbeat函数。

定期发送心跳消息 (sendHeartbeat)

  1. 心跳间隔设置 :心跳消息的发送间隔被设置为10秒(HEARTBEAT_INTERVAL = 10000),这意味着服务器每10秒向所有客户端发送一次心跳消息。
  2. 遍历客户端 :通过遍历rooms对象中的所有房间及其内的客户端,服务器能够遍历到所有当前连接的客户端。rooms对象看起来是以房间ID为键、客户端集合(Set)为值的结构。
  3. 检查心跳响应:对于每个客户端,代码首先检查自上次发送心跳消息以来是否已经超过了两倍的心跳间隔时间,并且没有收到客户端的心跳响应。如果是这样,服务器将认为这是一个"死连接",随即关闭该连接,并从其所在房间中移除。
  4. 发送心跳消息 :对于状态为OPEN的WebSocket连接(活跃连接),服务器更新lastHeartbeatSent为当前时间,并发送心跳消息给客户端。心跳消息是一个JSON字符串,包含类型typeheartbeat和消息messageping的对象。

处理客户端心跳响应 (handleHeartbeat)

当客户端收到心跳消息并响应后,服务器端的handleHeartbeat函数被调用。

  1. 更新心跳响应时间 :此函数更新socket.lastHeartbeatReceived为当前时间,表示服务器最后一次成功接收到该客户端的心跳响应的时间。
  2. 日志记录 :记录一条日志信息,指出从哪个用户(userId)以及哪个房间(room)接收到了心跳响应。这有助于监控系统的运行状况和用户活跃度。

写在最后

到目前为止,我们通过实现心跳机制,确保了WebSocket连接的活性和健康状态,同时也为系统引入了周期性的负载。虽然这种方法在一定程度上增加了服务器的负载(类似于自我实施的每10秒一次的DDoS攻击),但它是维持实时通信系统稳定性和可靠性的关键手段。通过定期清理不活跃的连接,我们不仅优化了资源的使用[手动狗头],还提高了系统的整体性能和响应能力。

确实有点问题,我们在下一章修复它

相关推荐
2401_857622663 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_857589363 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
哎呦没5 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
杨哥带你写代码6 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
AskHarries7 小时前
读《show your work》的一点感悟
后端
A尘埃7 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23077 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
Marst Code7 小时前
(Django)初步使用
后端·python·django
代码之光_19807 小时前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端