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#
Property | Required | Type | Default | Description |
---|---|---|---|---|
url | true | string | none | WebSocket server interface address |
pingTimeout | false | number | 8000 | Interval for sending heartbeat packets |
pongTimeout | false | number | 15000 | Maximum interval for not receiving messages |
reconnectTimeout | false | number | 4000 | Interval for each reconnection |
reconnectLimit | false | number | 15 | Maximum number of reconnections |
pingMsg | false | string | "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 ฅ●ω●ฅ