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