WebSocket was proposed in 2008 and its communication protocol was formulated as a standard in 2011

Unlike HTTP, Websocket supports full-duplex communication (that is, two-way communication between the client and the service). Before WebSocket, clients and servers often maintained long links using methods such as HTTP polling and Comet

However, there is no doubt that doing so is a resource drain on the server because HTTP requests contain long header files that pass little useful information.

So Websocket was born. It not only saves resources and bandwidth, but also can realize the function of long link. Only the client takes the initiative to shake hands with the server once, it can carry out real-time communication and realize push technology.

I have also written a related article before: Socket chat room, using JS+ socke. IO +WebRTC+ nodeJS + Express to build a simple version of remote video chat, but the module used is socke. IO, and not deeply optimized, when I really use it in my daily work, I find things are not easy. Sometimes the front end or back end will be disconnected without the other party knowing, such as weak network or back end server restart, the front end can not guarantee the connection. Therefore, in this article, we will use Websocket to make a simple demo, and add heartbeat and disconnection reconnection functions

The first is the server side, using Node +Ws moduleSet up websocket service, create server.js under server folder, and download WS module after NPM initialization

npm init -y
npm i ws
Copy the code

Introduce WS module, and build a simple Websocket service

const WebSocket = require('ws');
const port = 1024/ / port
const pathname = '/ws/'// Access path

new WebSocket.Server({port}, function () {
    console.log('Websocket service enabled')
}).on('connection', connectHandler)

function connectHandler (ws) {
    console.log('Client connection')
    ws.on('error', errorHandler)
    ws.on('close', closeHandler)
    ws.on('message', messageHandler)
}

function messageHandler (e) {
    console.info('Receive client message')
    this.send(e)
}

function errorHandler (e) {
    console.info('Client error')}function closeHandler (e) {
    console.info('Client is disconnected')}Copy the code

The front-end part also builds a WS access client

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        Title</title>
</head>
<body>
<script type="module">
    const name = 'test'// Connect to user name
    let wsUrl = 'the ws: / / 127.0.0.1:1024 / ws /? name=' + name
    const ws = new WebSocket(wsUrl)
    ws.onopen = function (e) {
        console.log('open')
        ws.send(JSON.stringify({
            ModeCode: "message".msg: 'hello'}}))// Call back when connecting
    ws.onclose = function (e) {
        console.log('off')}// Callback when disconnected
    ws.onmessage = function (e) {
        let data = JSON.parse(e.data)
        console.log('Received message' + data.msg)
        ws.close()
    }// Received the server message
    ws.onerror = function (e) {
        console.log('wrong')}// Connection error
</script>
</body>
</html>
Copy the code

Front-end print results:



Server print result:



With the above effect, one of the simplest WS connections is implemented. Now, let’s optimize it. In order to reduce coupling, let’s first introduceeventBusPublish the subscription, and then create a new WebSocket class that inherits from the native WebSocket, because we’re going to do heartbeat and reconnection in it

On the server side, let’s first refine the server and verify the WS connection through the UPGRADE filter of HTTP

Add the HTTP service on the original server and perform path verification

const http = require('http');
const server = http.createServer()

server.on("upgrade".(req, socket, head) = > {// Filter data through http.server
    let url = new URL(req.url, `http://${req.headers.host}`)
    let name = url.searchParams.get('name')// Get the connection id
    if(! checkUrl(url.pathname, pathname)) {// Do not comply with the standard
        socket.write('Not standard access');
        socket.destroy();
        return;
    }
})
server.listen(port, () = > {
    console.log('Service on')})// Verify url standards
function checkUrl (url, key) {// Check whether the URL contains the key
    return - ~ url.indexOf(key)
}
Copy the code

After completing httpServer, we will improve the Websocket service, save each connected user through the proxy and realize the addition and deletion, get the following complete server

const http = require('http');
const WebSocket = require('ws');
const port = 1024/ / port
const pathname = '/ws/'// Access path
const server = http.createServer()

class WebSocketServer extends WebSocket.Server {
    constructor () {
        super(... arguments);this.webSocketClient = {}// Store the connected client
    }

    set ws (val) {// Proxy for the current ws, which is initialized when assigned
        this._ws = val
        val.t = this;
        val.on('error'.this.errorHandler)
        val.on('close'.this.closeHandler)
        val.on('message'.this.messageHandler)
    }

    get ws () {
        return this._ws
    }

    messageHandler (e) {
        console.info('Receive client message')
        let data = JSON.parse(e)
        switch(data.ModeCode) {
            case 'message':
                console.log('Received message' + data.msg)
                this.send(e)
                break;
            case 'heart_beat':
                console.log(` receivedThe ${this.name}The heartbeat${data.msg}`)
                this.send(e)
                break;
        }
    }

    errorHandler (e) {
        this.t.removeClient(this)
        console.info('Client error')
    }

    closeHandler (e) {
        this.t.removeClient(this)
        console.info('Client is disconnected')
    }

    addClient (item) {// Add the device to the client list when it goes online
        if(this.webSocketClient[item['name']]) {
            console.log(item['name'] + 'Client already exists')
            this.webSocketClient[item['name']].close()
        }
        console.log(item['name'] + 'Client added')
        this.webSocketClient[item['name']] = item
    }

    removeClient (item) {// The device is removed from the client list when disconnected
        if(!this.webSocketClient[item['name']]) {
            console.log(item['name'] + 'Client does not exist')
            return;
        }
        console.log(item['name'] + 'Client removed')
        this.webSocketClient[item['name']] = null}}const webSocketServer = new WebSocketServer({noServer: true})
server.on("upgrade".(req, socket, head) = > {// Filter data through http.server
    let url = new URL(req.url, `http://${req.headers.host}`)
    let name = url.searchParams.get('name')// Get the connection id
    if(! checkUrl(url.pathname, pathname)) {// Do not comply with the standard
        socket.write('Not standard access');
        socket.destroy();
        return;
    }
    webSocketServer.handleUpgrade(req, socket, head, function (ws) {
        ws.name = name// Add an index to query a socket connection in the client list
        webSocketServer.addClient(ws);
        webSocketServer.ws = ws
    });
})
server.listen(port, () = > {
    console.log('Service on')})// Verify url standards
function checkUrl (url, key) {// Check whether the URL contains the key
    return - ~ url.indexOf(key)
}
Copy the code

When the connection is disconnected, only the client can actively access the server to realize reconnection, so the client has more functions than the server. We optimize the websocket of the client and add some simple buttons for control functions (connect, send messages, disconnect). Here we need to pay attention to: The current connection must be closed before the next connection. Otherwise, multiple clients may be connected at the same time, consuming performance

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        Title</title>
</head>
<body>
<button id="connect">The connection</button>
<button disabled
        id="sendMessage">send</button>
<button disabled
        id="destroy">Shut down</button>
<script type="module">
    const name = 'test'// Connect to user name
    let connect = document.querySelector('#connect'),// Connect button
        sendMessage = document.querySelector('#sendMessage'),// Send button
        destroy = document.querySelector('#destroy'),// Close the button
        wsUrl = 'the ws: / / 127.0.0.1:1024 / ws /? name=' + name,// Connection address
        ws;

    connect.addEventListener('click', connectWebSocket)
    sendMessage.addEventListener('click'.function (e) {
        ws.send(JSON.stringify({
            ModeCode: "message".msg: 'hello'
        }))
    })
    destroy.addEventListener('click'.function (e) {
        ws.close()
        ws = null
    })

    function connectWebSocket () {
        if(! ws) {// Execute for the first time, initialized or when WS is disconnected
            ws = new WebSocket(wsUrl)
            initWebSocket()
        }
    }

    function initWebSocket () {
        ws.onopen = function (e) {
            setButtonState('open')
            console.log('open')}// Call back when connecting
        ws.onclose = function (e) {
            setButtonState('close')
            console.log('off')}// Callback when disconnected
        ws.onmessage = function (e) {
            let data = JSON.parse(e.data)
            console.log('Received message' + data.msg)
        }// Received the server message
        ws.onerror = function (e) {
            setButtonState('close')
            console.log('wrong')}// Connection error
    }

    @param state: open indicates the enabled state, close indicates the closed state */
    function setButtonState (state) {
        switch(state) {
            case 'open':
                connect.disabled = true
                sendMessage.disabled = false
                destroy.disabled = false
                break;
            case 'close':
                connect.disabled = false
                sendMessage.disabled = true
                destroy.disabled = true
                break; }}</script>
</body>
</html>
Copy the code

The effect is as follows:

At this stage, the Demo of websocket can be run manually. On this basis, we will encapsulate it and use it for external communication through eventBus. The specific process will be realized together with the following heartbeat

Websocket heartbeat mechanism: as the name implies, is the client every once in a while to the server sends a heartbeat message, just put the message return after each server messages are received, at this point, if the two still keep connection, the client will receive the message, if received, then disconnects, at this point, the client will be active reconnection, complete a cycle

The implementation of heartbeat is also very simple. You only need to use the callback function to delay the connection for the first time. At this time, you need to set a heartbeat timeout period. Now, I’m going to do a heartbeat

    //this. HeartBeat --> time: heartBeat interval timeout: heartBeat interval
    /* * heartbeat initial function * @param time: heartbeat interval */
    function startHeartBeat (time) {
        setTimeout(() = > {
            this.sendMsg({
                ModeCode: ModeCode.HEART_BEAT,
                msg: new Date()})this.waitingServer()
        }, time)
    }
    // Wait for a response from the server and check whether the connection is successful through the webSocketState
    function waitingServer () {
        this.webSocketState = false// Online status
        setTimeout(() = > {
            if(this.webSocketState) {
                this.startHeartBeat(this.heartBeat.time)
                return
            }
            console.log('Heartbeat unresponsive, disconnected')
            this.close()
            // Reconnection operation
        }, this.heartBeat.timeout)
    }
Copy the code

Once the heartbeat is implemented, just call ws-onopen and the effect is as follows:





Then there’s the reconnect part, which is actually just a new delayed callback, similar to the heartbeat, except that it’s used when the connection fails, which I won’t explain here. Here is the full version of the code:

Websocket parts:

import eventBus
    from "./eventBus.js"

const ModeCode = {// WebSocket message type
    MSG: 'message'.// Common message
    HEART_BEAT: 'heart_beat'/ / the heart
}

export default class MyWebSocket extends WebSocket {
    constructor (url, protocols) {
        super(url, protocols);
        return this
    }

    /* * entry function * @param heartBeatConfig time: heartbeat interval timeout: heartbeat timeout reconnect: heartbeat timeout * @param isReconnect Whether to reconnect the connection */
    init (heartBeatConfig, isReconnect) {
        this.onopen = this.openHandler// Call back when connecting
        this.onclose = this.closeHandler// Callback when disconnected
        this.onmessage = this.messageHandler// Received the server message
        this.onerror = this.errorHandler// Connection error
        this.heartBeat = heartBeatConfig
        this.isReconnect = isReconnect
        this.reconnectTimer = null// Reconnect the timer
        this.webSocketState = false// The socket status true indicates that the socket is connected
    }

    openHandler () {
        eventBus.emitEvent('changeBtnState'.'open')// Trigger events to change button styles
        this.webSocketState = true// The socket state is set to connect and serves as an interceptor for subsequent disconnection reconnections
        this.heartBeat && this.heartBeat.time ? this.startHeartBeat(this.heartBeat.time) : ""// Whether to enable the heartbeat mechanism
        console.log('open')
    }

    messageHandler (e) {
        let data = this.getMsg(e)
        switch(data.ModeCode) {
            case ModeCode.MSG:// Common message
                console.log('Received message' + data.msg)
                break;
            case ModeCode.HEART_BEAT:/ / the heart
                this.webSocketState = true
                console.log('Heartbeat response received' + data.msg)
                break;
        }
    }

    closeHandler () {/ / socket closed
        eventBus.emitEvent('changeBtnState'.'close')// Trigger events to change button styles
        this.webSocketState = false// The socket status is set to disconnected
        console.log('off')
    }

    errorHandler () {/ / socket error
        eventBus.emitEvent('changeBtnState'.'close')// Trigger events to change button styles
        this.webSocketState = false// The socket status is set to disconnected
        this.reconnectWebSocket()/ / reconnection
        console.log('wrong')
    }

    sendMsg (obj) {
        this.send(JSON.stringify(obj))
    }

    getMsg (e) {
        return JSON.parse(e.data)
    }

    /* * heartbeat initial function * @param time: heartbeat interval */
    startHeartBeat (time) {
        setTimeout(() = > {
            this.sendMsg({
                ModeCode: ModeCode.HEART_BEAT,
                msg: new Date()})this.waitingServer()
        }, time)
    }

    // Wait for a response from the server and check whether the connection is successful through the webSocketState
    waitingServer () {
        this.webSocketState = false
        setTimeout(() = > {
            if(this.webSocketState) {
                this.startHeartBeat(this.heartBeat.time)
                return
            }
            console.log('Heartbeat unresponsive, disconnected')
            try {
                this.close()
            } catch(e) {
                console.log('Connection closed, no need to close')}this.reconnectWebSocket()
        }, this.heartBeat.timeout)
    }

    // Reconnection operation
    reconnectWebSocket () {
        if(!this.isReconnect) {
            return;
        }
        this.reconnectTimer = setTimeout(() = > {
            eventBus.emitEvent('reconnect')},this.heartBeat.reconnect)
    }
}
Copy the code

Index.html parts:

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        Title</title>
</head>
<body>
<button id="connect">The connection</button>
<button disabled
        id="sendMessage">send</button>
<button disabled
        id="destroy">Shut down</button>
<script type="module">
    import eventBus
        from "./js/eventBus.js"
    import MyWebSocket
        from './js/webSocket.js'

    const name = 'test'// Connect to user name
    let connect = document.querySelector('#connect')
    let sendMessage = document.querySelector('#sendMessage')
    let destroy = document.querySelector('#destroy')
    let myWebSocket,
        wsUrl = 'the ws: / / 127.0.0.1:1024 / ws /? name=' + name

    eventBus.onEvent('changeBtnState', setButtonState)// Set the button style
    eventBus.onEvent('reconnect', reconnectWebSocket)// Receive the reconnection message
    connect.addEventListener('click', reconnectWebSocket)
    sendMessage.addEventListener('click'.function (e) {
        myWebSocket.sendMsg({
            ModeCode: "message".msg: 'hello'
        })
    })
    destroy.addEventListener('click'.function (e) {
        myWebSocket.close()
    })

    function reconnectWebSocket () {
        if(! myWebSocket) {// Initialization for the first time
            connectWebSocket()
        }
        if(myWebSocket && myWebSocket.reconnectTimer) {// Prevent multiple websockets from executing simultaneously
            clearTimeout(myWebSocket.reconnectTimer)
            myWebSocket.reconnectTimer = null
            connectWebSocket()
        }
    }

    function connectWebSocket () {
        myWebSocket = new MyWebSocket(wsUrl);
        myWebSocket.init({//time: indicates the heartbeat timeout interval. Timeout: indicates the heartbeat timeout interval
            time: 30 * 1000.timeout: 3 * 1000.reconnect: 10 * 1000
        }, true)}@param state: open indicates the enabled state, close indicates the closed state */
    function setButtonState (state) {
        switch(state) {
            case 'open':
                connect.disabled = true
                sendMessage.disabled = false
                destroy.disabled = false
                break;
            case 'close':
                connect.disabled = false
                sendMessage.disabled = true
                destroy.disabled = true
                break; }}</script>
</body>
</html>
Copy the code

The end result is that the client can remain reconnected even if the back-end service is down or the network is disconnected



Finally, thank you for reading here. Welcome to point out and discuss any questions in this article

Attached source code:Gitee