banner
 Sayyiku

Sayyiku

Chaos is a ladder
telegram
twitter

WebSocket Heartbeat Reconnection Mechanism

WebSocket is a network communication protocol that makes data exchange between clients and servers simpler. Recently, I used WebSocket in a project to implement a simple online chat room feature, and now I will explore the mechanism of heartbeat reconnection.

WebSocket#

WebSocket allows the server to actively push data to the client. Many websites used polling to achieve push technology in the past, which was not only inefficient but also wasted a lot of bandwidth and other resources. HTML5 defines the WebSocket protocol, which can better save server resources and bandwidth, and enable real-time communication.

Advantages of WebSocket:

  • Less control overhead
  • Stronger real-time capability
  • Maintains connection state
  • Better binary support
  • Supports extensions
  • Better compression effect

The biggest advantage of WebSocket is that it can maintain a long connection between the front-end and back-end messages. However, in some cases, the long connection may fail without timely feedback, and the front-end is unaware that the connection has been disconnected. For example, if the user's network is disconnected, it will not trigger any event functions of WebSocket. In this case, if a message is sent, it will not be sent out, and the browser will immediately or after a short period of time (different browsers or browser versions may behave differently) trigger the onclose function.

To avoid this situation and ensure connection stability, the front-end needs to perform certain optimization processing, usually using the heartbeat reconnection solution. The front-end and back-end agree that the front-end sends a heartbeat packet at regular intervals, and the back-end returns a response packet after receiving the heartbeat packet to inform the front-end that the connection is normal. If no message is received within a certain period of time, the connection is considered disconnected and the front-end performs reconnection.

Heartbeat Reconnection#

Based on the above analysis, the key to implementing heartbeat reconnection is to send heartbeat messages on time, detect response messages, and determine whether to reconnect. Therefore, the following four goals are set:

  • Ability to send heartbeat packets at regular intervals
  • Automatically reconnect when there is a connection error or closure
  • If no message is received within a certain time interval, it is considered disconnected and automatically reconnects
  • Ability to customize heartbeat messages and set the maximum number of reconnections

0x01 Initialization#

For ease of reuse, here it is decided to encapsulate the WebSocket management as a utility class WebsocketHB, and customize the heartbeat reconnection mechanism by passing in a configuration object.

class WebsocketHB {
  constructor({
    url, // Client connection address
    pingTimeout = 8000, // Interval for sending heartbeat packets, default 8000 milliseconds
    pongTimeout = 15000, // Longest interval for not receiving messages, default 15000 milliseconds
    reconnectTimeout = 4000, // Interval for each reconnection
    reconnectLimit = 15, // Maximum number of reconnections
    pingMsg // Heartbeat packet message content
  }) {
    // Initialize configuration
    this.url = url
    this.pingTimeout = pingTimeout
    this.pongTimeout = pongTimeout
    this.reconnectTimeout = reconnectTimeout
    this.reconnectLimit = reconnectLimit
    this.pingMsg = pingMsg

    this.ws = null
    this.createWebSocket()
  }

  // Create WebSocket
  createWebSocket() {
    try {
      this.ws = new WebSocket(this.url)
      this.ws.onclose = () => {
        this.onclose()
      }
      this.ws.onerror = () => {
        this.onerror()
      }
      this.ws.onopen = () => {
        this.onopen()
      }
      this.ws.onmessage = event => {
        this.onmessage(event)
      }
    } catch (error) {
      console.error('WebSocket connection failed!', error)
      throw error
    }
  }

  // Send message
  send(msg) {
    this.ws.send(msg)
  }
}

Usage:

const ws = new WebsocketHB({
  url: 'ws://xxx'
})

ws.onopen = () => {
  console.log('connect success')
}
ws.onmessage = e => {
  console.log(`onmessage: ${e.data}`)
}
ws.onerror = () => {
  console.log('connect onerror')
}
ws.onclose = () => {
  console.log('connect onclose')
}
ws.send('Hello, chanshiyu!')

0x02 Sending Heartbeat Packets and Reconnection#

Here, setTimeout is used to simulate setInterval for sending heartbeat packets at regular intervals to avoid blocking the timer queue. The maximum number of reconnections is limited. It should be noted that when reconnecting, a lock is added to avoid unnecessary reconnections. In addition, when receiving a message, the longest interval message reconnection timer is cleared. If a message can be received, it means that the connection is normal and there is no need to reconnect.

class WebsocketHB {
  constructor() {
    // ...
    // Instance variables
    this.ws = null
    this.pingTimer = null // Heartbeat packet timer
    this.pongTimer = null // Message reception timer
    this.reconnectTimer = null // Reconnection timer
    this.reconnectCount = 0 // Current number of reconnections
    this.lockReconnect = false // Lock reconnection
    this.lockReconnectTask = false // Lock reconnection task queue

    this.createWebSocket()
  }

  createWebSocket() {
    // ...
    this.ws = new WebSocket(this.url)
    this.ws.onclose = () => {
      this.onclose()
      this.readyReconnect()
    }
    this.ws.onerror = () => {
      this.onerror()
      this.readyReconnect()
    }
    this.ws.onopen = () => {
      this.onopen()

      this.clearAllTimer()
      this.heartBeat()
      this.reconnectCount = 0
      // Unlock, can reconnect
      this.lockReconnect = false
    }
    this.ws.onmessage = event => {
      this.onmessage(event)

      // Timeout timer
      clearTimeout(this.pongTimer)
      this.pongTimer = setTimeout(() => {
        this.readyReconnect()
      }, this.pongTimeout)
    }
  }

  // Send heartbeat packets
  heartBeat() {
    this.pingTimer = setTimeout(() => {
      this.send(this.pingMsg)
      this.heartBeat()
    }, this.pingTimeout)
  }

  // Prepare for reconnection
  readyReconnect() {
    // Avoid cyclic reconnection, do not reconnect when a reconnection task is in progress
    if (this.lockReconnectTask) return
    this.lockReconnectTask = true
    this.clearAllTimer()
    this.reconnect()
  }

  // Reconnection
  reconnect() {
    if (this.lockReconnect) return
    if (this.reconnectCount > this.reconnectLimit) return

    // Lock, disable reconnection
    this.lockReconnect = true
    this.reconnectCount += 1
    this.createWebSocket()
    this.reconnectTimer = setTimeout(() => {
      // Unlock, can reconnect
      this.lockReconnect = false
      this.reconnect()
    }, this.reconnectTimeout)
  }}

  // Clear all timers
  clearAllTimer() {
    clearTimeout(this.pingTimer)
    clearTimeout(this.pongTimer)
    clearTimeout(this.reconnectTimer)
  }
}

0x03 Instance Destruction#

Finally, add a destroy method to the utility class to set a lock to prevent reconnection when the instance is destroyed, clear all timers, and close the long connection.

class WebsocketHB {
  // Reconnection
  reconnect() {
    if (this.forbidReconnect) return
    //...
  }

  // Destroy the WebSocket
  destroyed() {
    // If the connection is manually closed, do not reconnect
    this.forbidReconnect = true
    this.clearAllTimer()
    this.ws && this.ws.close()
  }
}

Packaging as an npm Package#

At this point, the WebSocket utility class with heartbeat reconnection functionality is basically encapsulated. You can try to use it now. The final completed code is uploaded to GitHub, see websocket-heartbeat. It is also packaged and uploaded to npm for future use in projects. If you are interested, you can try it out at websockethb.

Installation#

npm install websockethb

Import and Usage#

import WebsocketHB from 'websockethb'

const ws = new WebsocketHB({
  url: 'ws://xxx'
})

ws.onopen = () => {
  console.log('connect success')
}
ws.onmessage = e => {
  console.log(`onmessage: ${e.data}`)
}
ws.onerror = () => {
  console.log('connect onerror')
}
ws.onclose = () => {
  console.log('connect onclose')
}

Properties#

PropertyRequiredTypeDefaultDescription
urltruestringnoneWebSocket server interface address
pingTimeoutfalsenumber8000Interval for sending heartbeat packets
pongTimeoutfalsenumber15000Maximum interval for not receiving messages
reconnectTimeoutfalsenumber4000Interval for each reconnection
reconnectLimitfalsenumber15Maximum number of reconnections
pingMsgfalsestring"heartbeat"Heartbeat packet message content
const opts = {
  url: 'ws://xxx',
  pingTimeout: 8000, // Interval for sending heartbeat packets, default 8000 milliseconds
  pongTimeout: 15000, // Longest interval for not receiving messages, default 15000 milliseconds
  reconnectTimeout: 4000, // Interval for each reconnection
  reconnectLimit: 15, // Maximum number of reconnections
  pingMsg: 'heartbeat' // Heartbeat packet message content
}
const ws = new WebsocketHB(opts)

Sending Messages#

ws.send('Hello World')

Disconnecting#

ws.destroyed()

Just enjoy it ฅ●ω●ฅ

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.