The first sample
I don’t like the demo. I’ll play
Click here to view the source code
Websocket is introduced
background
In many scenarios, the user needs to get real-time information, such as chatting, reading medical equipment and so on, the old way of solution is based on polling for the latest data, but will not be fully synchronous real-time news, and most of the time the request is not necessary, instead of wasting a lot of traffic and server resources. Based on this background, Websocket was born.
Basic concepts of Websocket
WebSocket is a protocol for full duplex communication over a single TCP connection provided by HTML5. WebSocket communication protocol was born in 2008 and became an international standard in 2011. WebSocket makes it easier to exchange data between the client and the server, allowing the server to actively push data to the client. In the WebSocket API, the browser and server only need to complete a handshake to create a persistent connection and two-way data transfer.
-
Compatibility issues (supported by all major browsers)
Websocket characteristics
- Control costs. When data is exchanged between the server and client after the connection is created, the packet headers used for protocol control are relatively small. Without extensions, this header size is only 2 to 10 bytes (depending on packet length) for server-to-client content; For client-to-server content, an additional 4-byte mask is required for this header. This overhead is significantly reduced compared to HTTP requests that carry the full header each time.
- Real-time communication. Because the protocol is full-duplex, the server can proactively send data to the client at any time. Compared with HTTP requests that need to wait for the client to initiate the request before the server can respond, the latency is significantly less; Even compared to long polling like Comet, data can be delivered more times in a shorter time.
- Keep the connection state. Unlike HTTP, Websocket needs to create a connection first, which makes it a stateful protocol that can then communicate without some state information.
- Binary transmission is supported. You can send text or binary data. Websocket defines binary frames, making it easier to process binary content than HTTP.
- The protocol identifier is WS (or WSS if encrypted), and the server URL is the URL.
- Simple implementation. Based on TCP protocol, the implementation of the server side is relatively easy, and there is no homology limit, the client can communicate with any server.
- It has good compatibility with HTTP protocol. The default ports are also 80 and 443, and the handshake phase uses HTTP protocol, so it is not easy to mask the handshake and can pass various HTTP proxy servers.
- Support for extensions. Websocket defines extensions that users can extend and implement partially customized sub-protocols. For example, some browsers support compression.
To put it simply, Websocket has the characteristics of two-way communication, strong real-time performance, binary support and cost control.
Similarities and differences between Websocket and HTTP
-
The same
- Both are application layer protocols based on TCP.
- Request/Response model is used to establish the connection.
- Errors are handled the same way during connection establishment, and WebSocket may return the same return code as HTTP at this stage.
-
different
-
HTTP is based on Request/Response and can only perform one-way transmission. It is half duplex communication, while WebSocket is full duplex communication
- Half-duplex communication: one-way traffic. The server does not actively push data to the client
- Full-duplex communication: The server can actively push information to the client, and the client can also actively send information to the server, which is a real bidirectional equal dialogue and belongs to the server push technology.
-
HTTP is stateless, so the request is closed when it receives a response. The benefit of statelessness is that the server does not need to store relevant session information. The disadvantage is that each HTTP request and response sends redundant information about the request; However, WebSocket only needs to establish a Request/Response message pair once, and then all TCP connections are made, avoiding redundant header information caused by multiple Request/Response message pair establishment. Saves a lot of traffic and server resources.
- When WebSocket establishes a handshake connection, data is transmitted through HTTP protocol, but after the connection is established, the real data transmission stage does not need HTTP protocol. HTTP requires three handshakes.
- The data transmitted by WebSocket is binary stream, which is in the unit of frame, while HTTP is plaintext transmission, which is string transmission.
-
Websocket and socket
- Socket is at the transport control layer, and Websokect is an application layer protocol
- Socket is actually the TCP/IP protocol encapsulation, socket itself is not a protocol, but the call interface, through the socket we can use TCP/IP
- Socket is the intermediate software abstraction layer of communication between application layer and TCP/IP protocol family. It is a group of interfaces. When two hosts communicate, let the socket organize the data to conform to the specified protocol. TCP relies more on the underlying IP protocol
Software communication has seven layers. The structure of the lower three layers is biased towards data communication, the upper three layers are more biased towards data processing, and the intermediate transmission layer is the bridge connecting the upper three layers and the lower three layers. Each layer does different work, and the upper layer protocol depends on the lower layer protocol.
Websocket application scenarios
- Social chat: such as QQ, wechat, fish ear chat room
- Live: such as betta fish, fish ear live and so on
- Intelligent customer service
- System message notification: Cargo pull
- Others: medical device readings, stock prices, traffic maps,……
Websocket startup and API
Set up a simple Websocket connection using WS. Ws is a third-party Websocket communication module built on Node.js. www.jmjc.tech/less/114
Simple to start
Install the ws
npm i ws
Copy the code
The service side
const Websocket = require('ws')
const wss = new Websocket.Server({ port: 3000 })
wss.on('connection'.function (ws) {
ws.on('message'.function (message) {
console.log('server receive message: ', message.toString());
})
ws.send('msg from server! ')})Copy the code
Client (here node starts to introduce ws example)
const Websocket = require('ws')
const ws = new Websocket('ws://localhost:3000')
ws.on('open'.function () {
ws.on('message'.function (msg) {
console.log('client receive message: : ', msg.toString())
})
ws.send('msg from client')})Copy the code
Websocket API
Websocket upgrades HTTP protocol to WebSocket protocol during the first handshake between the client and server. After the connection is established, subsequent messages are directly sent back and forth on the methods defined by the WebSocket interface.
The constructor
First, we call the WebSocket constructor to create a WebSocket connection that returns an instance of the WebSocket object.
The WebSocket protocol defines two URL schemes
- Ws: unencrypted
- WSS: encryption (using the security mechanism used by HTTPS to secure HTTP connections).
Create an instance
/ * * * URL: connection target * separate protocols (optional) : string | string [] name * / one or a set of agreement
const ws = new WebSocket(URL, protocols)
Copy the code
WebSocket instance
The WebSocket protocol is essentially a TCP – based protocol.
To establish a WebSocket connection, the client browser first issues an HTTP request to the server. This request is different from the usual HTTP request and contains some additional header information, including “Upgrade: “WebSocket” indicates that this is an HTTP request for a protocol upgrade. The server parses these additional headers and generates a reply message back to the client. The WebSocket connection between the client and the server is established, and the two sides can communicate freely through the connection channel. And the connection persists until either the client or the server actively closes the connection.
Websocket event
Websocket is purely event-driven and can handle data input and connection state changes by listening for events on Websocket objects.
The Websocket object has only four events
- Onopen: Triggered after the client and server establish a connection. It is called the initial handshake between client and server. If open is received, the connection is successful and communication can be started.
- Onmessage: Triggered when a message is received. Messages sent by the server to clients can include plain text messages, binary data (Blob messages or ArrayBuffer messages)
- Onerror: Triggered in response to an unexpected failure, which always terminates the connection after an error.
- Onclose: triggered when the connection is closed. Once the connection is closed, the client and server will not send or receive messages. You can also actively close the connection by calling the close() method.
Websocket method
- Send () : indicates that a message is sent before the connection is closed. (The message can be sent after onOpen and before onclose.)
- Close () : closes the connection
Websocket object properties
- ReadyState: Read-only property, indicating the connection status of Websocket. The values are as follows:
Constant feature | The values | state |
---|---|---|
Websocket.CONNECTING | 0 | Connection in progress, but not yet established |
Websocket.OPEN | 1 | The connection has been established and messages can be sent normally |
Websocket.CLOSING | 2 | Connection closing handshake in progress |
Websocket.CLOSED | 3 | Indicates that the connection is closed or cannot be opened. |
Note: different browsers support different, pro test wechat applets do not have this attribute.
- BufferedAmount: Read-only property. The number of UTF-8 text bytes that have been put into a queue by Send () for transmission, but have not yet been sent.
When checking the amount of buffered data sent to the server, especially when clients send large amounts of data to the server. While calling send() connections works immediately, data is not transferred over the Internet. The browser will cache the stack data for your client application so you can call Send () at any time, but if you want to know the rate at which data is being transferred over the network, the Websocket object can tell you the cache size. So you can use bufferedAmount to check the number of bytes that have been queued but not yet sent to the server.
- Protocol: Enables the Protocol used during the handshake.
Websocket initial handshake
Each Websocket connection starts with an HTTP request, which is similar to any other request but contains a special prefix — Upgrade. Upgrade indicates that the client will Upgrade the connection to the Websocket protocol.
Before the handshake, Websocket follows the HTTP/1.1 protocol.
The client sends the request to upgrade to a Websocket, also known as the initial handshake. After the client sends an HTTP Upgrade request, the connection is considered successful until the server responds with the 101 status code, Upgrade, and SEC-websocket-accept. Otherwise, the connection fails. Here are the request headers and corresponding headers for the copied WebSocket handshake:
// The request header sent by the client
GET wss:HTTP / 1.1 / / www.example.cn/webSocket / / using the HTTPS protocol, the corresponding WSS request
Host: www.example.cn
Connection: Upgrade // Http1.1 messages with the upgrade header must contain a Connection header, indicating that anyone receiving the message will process the fields specified in connection before forwarding the message (i.e. not forwarding the upgrade domain).
Upgrade: websocket // Define the header field for the conversion protocol. If the server supports it, the client wants to use an established HTTP (TCP) connection
Sec-WebSocket-Version: 13 // List of WebSocket protocol versions supported by the client
Origin: http://example.cn // Origin For security purposes, to prevent cross-site attacks, browsers generally use this to identify the original domain.
Sec-WebSocket-Key: afmbhhBRQuwCLmnWDRWHxw== // The header is randomly generated by the client, and the server will use this field to assemble another key value into the handshake return message. Used for initial handshake of websocket from client to server to avoid kua protocol attack.
Sec-WebSocket-Protocol: chat, superchat // The header tells the client which protocol the application can useSec-websocket-extensions: permessage-deflate (negotiated using transport data compression); Client_max_window_bits (erasing the relative SIZE of the sliding window when using LZ77 compression)/ / header
// The response header sent by the server
HTTP/1.1 101
Server: nginx/1.122.
Date: Sat, 11 Aug 2018 13:21:27 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: sLMyWetYOwus23qJyUD/fa1hztc= Verify that the server understands the WebSocket protocolSec-WebSocket-Protocol: chat Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=15
/** sec-websocket-accept (1) Concatenate the SEC-websocket-key with the GUID defined in the protocol (2) encode the string generated in (1) with SHA1 (3) Encode the string generated in (2) with Base64 sec-websocket-accept to determine: (1) whether the server understands the Websocket protocol, if not, it will not return the correct sec-websocket-accept (2) return value is this request, not the previous cache */
Copy the code
Websocket message format
A message sent by a client and a server to each other, represented over the network by a binary syntax that marks the boundary between the messages and includes brief type information. To be precise, these binary precursors mark the boundary between another unit, the frame. A frame is a piece of data that can be combined to make up a message. A message typically has only one frame, but it can be composed of any number of frames.
Websocket application
After introducing the basic concept of Websocket, let’s get down to business. Let’s implement a chat room ourselves.
Server.js in the back is started with Node, and our main goal here is to implement front-end functionality.
Create a connection
home.vue
this.ws = new WebSocket('ws://localhost:3000')
console.log('before open'.this.ws.readyState) / / 0
this.ws.onopen = () = > { // Succeeded in listening to the connection
console.log('onopen'.this.ws.readyState)
this.roomOpen = true
this.ws.send(JSON.stringify({
userId: this.userName,
userName: this.nickname,
roomId: item.roomId,
roomName: item.name,
event: 'login'.// Send a login message to the server with the corresponding room information and user information}}))this.ws.onmessage = (message) = > { // Callback received message
console.log('onmessage', message)
console.log('message.data: ', message.data)
}
this.ws.onclose = () = > { // Listen for webSocket closure callback
console.log('onclose'.this.ws.readyState)
}
close () {
this.ws && this.ws.close() // Disconnect the websocket
}
Copy the code
server.js
const Websocket = require('ws')
const wss = new Websocket.Server({ port: 3000 })
wss.on('connection'.function (ws) { // There is a client connection
ws.on('message'.function (message) {
console.log('server receive message: ', message.toString())
})
ws.send('msg from server! ')
ws.on('close'.function (message) {
console.log('Disconnected', message)
})
})
Copy the code
Multiple chat rooms and statistics online status
If there is a new room, add a roomId to the array. If there is an existing room, add 1 to the number of people online based on the existing room
home.vue
this.ws.onopen = () = > {
console.log('onopen'.this.ws.readyState)
this.roomOpen = true
this.ws.send(JSON.stringify({
userId: this.userName,
userName: this.nickname,
roomId: item.roomId,
roomName: item.name,
event: 'login',}})),25000)}this.ws.onmessage = (message) = > { // Received room message
const data = JSON.parse(message.data)
this.onlineNum = data.num
if (data.event === 'login') { // There is a message about another user entering the room
this.msgList.push({
content: ` welcome${data.userName}Enter the${data.roomName}Room ~ `})},else if (data.event === 'logout') { // Other users left the room
console.log('logout', data)
this.msgList.push({
content: `${data.userName}Leave the room.})},else { // Common message
const self = this.userId === data.userId
if (self) return
this.msgList.push({
name: data.userName,
self: false.content: data.content,
})
}
}
Copy the code
server.js
ws.on('message'.function (message) {
console.log('server receive message: ', message.toString())
const data = JSON.parse(message.toString())
if (typeof ws.roomId === 'undefined' && data.roomId) {
ws.roomId = data.roomId
if (typeof group[ws.roomId] === 'undefined') {
group[ws.roomId] = 1
} else {
group[ws.roomId]++
}
}
data.num = group[ws.roomId]
wss.clients.forEach(client= > {
if (client.readyState === Websocket.OPEN && client.roomId === ws.roomId) {
client.send(JSON.stringify(data))
}
})
})
ws.on('close'.function (message) {
group[ws.roomId]-- // When the chat room is closed, reduce the number of online users by 1, and push the message of quitting the room to other clients to update the number of online users on the page
wss.clients.forEach(function each (client) {
if(client ! == ws && client.readyState === Websocket.OPEN && client.roomId === ws.roomId) { client.send(JSON.stringify({ ... ws.enterInfo,event: 'logout'.num: group[ws.roomId],
}))
}
})
})
Copy the code
View the initial message debug panel
Heart keep alive
Juejin. Cn/post / 684490…
In the long connection scenario, the client and server do not always communicate with each other. If the two sides do not communicate with each other for a long time, neither side knows the current status of the other side. Therefore, the two sides need to send a small packet to tell the other side that they are still alive.
As shown in the figure above, in the application layer, the client usually sends a heartbeat packet ping to the server, and the server responds with a PONG to indicate that both parties are alive and well.
For other purposes
- When detecting that the heartbeat of a client is not coming, the server can actively close the channel and make it offline.
- If a client detects that a server is not responding, the heartbeat can be reconnected to obtain a new connection.
A common example of the need to use liveability today is when a PC user registers with a host using Telnet using TCP/IP. If, at the end of the day, they simply turn off the power without logging off, they will be left with a semi-open connection. In Figure 18-16, we see that sending data over a semi-open connection causes a reset to be returned, but that is from the client that is sending the data. If the customer has disappeared, leaving a semi-open connection on the server and the server is waiting for data from the customer, the server will wait forever. The keepalive function attempts to detect this semi-open connection on the server side.
It is also important for the server to be aware of connection availability in a timely manner, both because the server needs to clean up invalid connections in order to lighten the load, and because the server needs to deal with player disconnection problems in game replicas.
home.vue
this.ws.onopen = () = > {
if (this.heartbeatTimer ! = = -1) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = -1
}
this.heartbeatTimer = setInterval(() = > {
if (this.heartBeatTimeoutJob ! = = -1) {
clearTimeout(this.heartBeatTimeoutJob)
this.heartBeatTimeoutJob = -1
}
this.heartBeatTimeoutJob = setTimeout(() = > {
console.log('Heartbeat timed out')},10000)
this.ws.send(JSON.stringify({
event: 'heartBeat'.content: 'ping',}))console.log('send ping')},5000)}this.ws.onmessage = (message) = > {
console.log('onmessage', message)
const data = JSON.parse(message.data)
console.log('message.data: ', data)
if (data.event === 'heartBeat' && data.content === 'pong') {
console.log('receive server pong')
if (this.heartBeatTimeoutJob ! = = -1) {
clearTimeout(this.heartBeatTimeoutJob)
this.heartBeatTimeoutJob = -1
}
return}}this.ws.onclose = () = > {
console.log('onclose'.this.ws.readyState)
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = -1
clearTimeout(this.heartBeatTimeoutJob)
this.heartBeatTimeoutJob = -1
}
Copy the code
Server.js
// The server should also have a timer to check whether the heartbeat times out. If the timer times out, the server disconnects
ws.on('message'.function (message) {
// console.log('wss', wss)
console.log('server receive message: ', message.toString())
const data = JSON.parse(message.toString())
if (data.event === 'login') {
ws.enterInfo = data
}
if (data.event === 'heartBeat' && data.content === 'ping') {
console.log('receive ping message')
ws.isAlive = true
ws.send(JSON.stringify({
event: 'heartBeat'.content: 'pong',}))return
}
if (typeof ws.roomId === 'undefined' && data.roomId) {
ws.roomId = data.roomId
if (typeof group[ws.roomId] === 'undefined') {
group[ws.roomId] = 1
} else {
group[ws.roomId]++
}
}
console.log('groun', group)
data.num = group[ws.roomId]
wss.clients.forEach(client= > {
if (client.readyState === Websocket.OPEN && client.roomId === ws.roomId) {
client.send(JSON.stringify(data))
}
})
})
Copy the code
The message will reach
- Message must reach is used to process the compatible processing of important messages that are not received by users due to network or server problems during long connection. In the following figure, (1) The direct message is lost and cannot be processed; (2)(3) The user can receive important messages by ACK means to improve the message reach rate.
-
The ACK mechanism is used to ensure that the message is received. When the client receives the message, it needs to send an ACK receipt to inform the server that the message has been received. If the server does not receive the ACK message from the client, the client does not receive the ACK message and resends the ack message to ensure that the user can receive the ack message. When a client needs to receive a message, the following situations may occur when ack is used to process the message:
-
normal
- After receiving the message, the user sends an ACK to the server. The server knows that the client has received the message, and the server does not push the message.
- The user does not receive an ACK message, and therefore does not send an ACK to the server. The server does not receive an ACK message, and resends the message. The user receives the message, and the message must be completed. (The purpose of the message)
-
Fault condition
- After receiving the message, the user sends an ACK to the server. The network is interrupted during the sending process. As a result, the server mistakenly thinks that the client has not received the message and resends the message, resulting in multiple duplicate messages displayed on the client. (The problem caused by the message must be reprocessed).
-
expand
Websocket applications also have offline message synchronization, roaming messages, etc., interested in more information.
The Demo source code
You can go to view and download the demo
home.vue
<template>
<div class="container">
<div class="navbar">
<div v-if="roomOpen" class="back" @click="back">return</div>
<div class="title">{{ title }}</div>
</div>
<div v-if=! "" roomOpen" class="openBg">
<div class="userInfo">
<div v-if=! "" nickname" class="nameInputBox">
<input type="text" class="nameInput" v-model="inputname" placeholder="Please enter a nickname" @keydown="nameKeydown" />
<div class="nameConfirmBtn" @click="certainName">confirm</div>
</div>
<div v-else class="nickname">Nickname: {{nickname}}</div>
</div>
<div class="cardList">
<div class="itemCard" @click="enterRoom(item)" v-for="(item, i) in roomList" :key="i">
<img class="pic" :src="item.frame">
<span class="text">{{ item.name }}</span>
</div>
</div>
</div>
<div v-else class="bgImage" >
<div class="roomInfo">
<div class="onLineBox">Nickname:<span class="roomNickname">{{ this.nickname }}</span>{{this.onlineNum}}</div>
</div>
<div class="msgBox" ref="msgWrap" @scroll="listScroll">
<div class="msgPanel" ref="msgList">
<div :class="['msgItem', item.self && 'selfItem']" v-for="(item, i) in msgList" :key="i">
<span v-if=! "" item.self && item.name" style="font-weight: 500; font-size: 15px; color: #1d688c">{{ item.name }} : </span>
<span :style="{ color: currentRoomInfo.msgColor }">{{ item.content }}</span>
<span v-if="item.self" style="font-weight: 500; font-size: 15px; color: #1d688c">: I</span>
</div>
</div>
</div>
<div class="msgFooter">
<input class="msgInput" type="text" v-model="inputMsg" placeholder="Please enter your speech." @keydown="sendInputKeydown" />
<div class="msgBtn" @click="sendMsg">To speak</div>
</div>
</div>
</div>
</template>
<script>
import { Toast } from 'mint-ui'
import smoothscroll from 'smoothscroll-polyfill'
import { throttle, debounce } from 'lodash'
import { roomList } from './config'
import { isScrollBottom } from '@/common/util.js'
smoothscroll.polyfill()
export default {
name: 'home',
data () {
return {
nickname: ' '.inputname: ' '.userId: ' ',
roomList,
msgList: [].roomOpen: false.ws: null.onlineNum: 0.inputMsg: ' '.currentRoomInfo: 0.heartbeatTimer: -1.heartBeatTimeoutJob: -1.msgWrapRef: null.scrollIsBottom: true.wrapperHeight: 0.isBindScrolled: false.isSending: false.msgListRef: null.scrollBottomTimeId: null,}},computed: {
title() {
return this.roomOpen ? this.currentRoomInfo.name : 'home'}},watch: {
msgList: {
deep: true.handler() {
this.msgChange()
},
},
roomOpen(val) {
if (val) {
this.$nextTick(() = > {
this.msgWrapRef = this.$refs.msgWrap
this.msgListRef = this.$refs.msgList
this.wrapperHeight = this.msgWrapRef.offsetHeight
})
}
},
},
mounted(){},methods: {
enterRoom (item) {
if (!this.nickname) return Toast('Come up with a nice name.')
this.navbarProps = { ... this.navbarProps,title: item.name }
this.currentRoomInfo = item
this.connect()
console.log('before open'.this.ws.readyState)
this.ws.onopen = () = > {
console.log('onopen'.this.ws.readyState)
this.roomOpen = true
this.ws.send(JSON.stringify({
userId: this.userName,
userName: this.nickname,
roomId: item.roomId,
roomName: item.name,
event: 'login',}))// Heartbeat timer logic
if (this.heartbeatTimer ! = = -1) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = -1
}
this.heartbeatTimer = setInterval(() = > {
if (this.heartBeatTimeoutJob ! = = -1) {
clearTimeout(this.heartBeatTimeoutJob)
this.heartBeatTimeoutJob = -1
}
this.heartBeatTimeoutJob = setTimeout(() = > {
console.log('Heartbeat timed out')
// Reconnection is required
}, 10000)
this.ws.send(JSON.stringify({
event: 'heartBeat'.content: 'ping',}))console.log('send ping')},25000)}this.ws.onmessage = (message) = > {
const data = JSON.parse(message.data)
if (data.event === 'heartBeat' && data.content === 'pong') {
console.log('receive server pong')
if (this.heartBeatTimeoutJob ! = = -1) {
clearTimeout(this.heartBeatTimeoutJob)
this.heartBeatTimeoutJob = -1
}
return
}
this.onlineNum = data.num
if (data.event === 'login') {
this.msgList.push({
content: ` welcome${data.userName}Enter the${data.roomName}Room ~ `})},else if (data.event === 'logout') {
console.log('logout', data)
this.msgList.push({
content: `${data.userName}Leave the room.})},else {
const self = this.userId === data.userId
if (self) return
this.msgList.push({
name: data.userName,
self: false.content: data.content,
})
}
}
this.ws.onclose = () = > {
this.removeAllTimeJob()
Toast('You have left your room')
this.roomOpen = false
this.msgList = []
this.onlineNum = 0
}
},
connect () {
this.ws = new WebSocket('ws://localhost:3000')
},
sendMsg () {
if (!this.inputMsg.trim().length) return Toast('Speech cannot be empty ~')
this.isSending = true
this.ws.send(JSON.stringify({
userName: this.nickname,
userId: this.userId,
roomId: this.currentRoomInfo.roomId,
roomName: this.currentRoomInfo.name,
content: this.inputMsg.trim(),
}))
// Local display by default
this.msgList.push({
content: `The ${this.inputMsg}`.name: this.nickname,
self: true,})this.inputMsg = ' '
setTimeout(() = > {
this.scrollBottom()
this.isSending = false
}, 0)
},
close () {
this.ws && this.ws.close()
},
back () {
if (!this.roomOpen) return
this.roomOpen = false
this.msgList = []
this.onlineNum = 0
this.close()
Toast({
content: 'You are out of the room'.duration: 1000,
})
},
certainName () {
if (this.inputname.trim().length) {
this.nickname = this.inputname.trim()
this.userId = `The ${+new Date()}`
} else {
Toast('Username cannot be empty! ')}},// Remove all timers
removeAllTimeJob() {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = -1
clearTimeout(this.heartBeatTimeoutJob)
this.heartBeatTimeoutJob = -1
},
msgChange() { // Listen for changes in the message list to automatically scroll to the latest messages
if (this.scrollBottomTimeId) {
clearTimeout(this.scrollBottomTimeId)
this.scrollBottomTimeId = null
}
setTimeout(() = > {
if (!this.scrollIsBottom) {
this.scrollBottom()
return
}
this.$nextTick(() = > {
const listHeight = this.msgListRef.offsetHeight
const diff = listHeight - this.wrapperHeight // The difference between list height and container height
const top = this.msgWrapRef.scrollTop // List scrolling height
if (diff - top > 10) {
if (this.isBindScrolled) {
this.isBindScrolled = false
this.msgWrapRef.removeEventListener('scroll'.this.addScroll, false)}this.msgWrapRef.scrollTo({
top: diff + 10.left: 0.behavior: 'smooth'})},else if (!this.isSending) {
// this.restMessage = true
if (!this.isBindScrolled) {
this.isBindScrolled = true
this.msgWrapRef.addEventListener('scroll'.this.addScroll, false)}}})},500);
},
addScroll() {
debounce(this.listScroll, 200)
this.isBindScrolled = true
},
scrollBottom() {
this.msgWrapRef.scrollTo({
top: this.msgListRef.offsetHeight,
left: 0.behavior: 'smooth',}}),listScroll() {
const ele = this.msgWrapRef
const isBottom = isScrollBottom(ele, ele.clientHeight, 50)
if (isBottom) {
this.scrollIsBottom = true
// this.restMessage = false
} else {
this.scrollIsBottom = false}},sendInputKeydown(e) {
let key = window.event.keyCode;
if (key == 13) {
this.sendMsg()
}
},
nameKeydown(e) {
let key = window.event.keyCode;
if (key == 13) {
this.certainName()
}
}
},
beforeDestroy() {
this.removeAllTimeJob()
this.close()
this.msgWrapRef && this.msgWrapRef.removeEventListener('scroll'.this.addScroll, false)}}</script>
<style scoped>
@import './index.scss';
</style>
Copy the code
server.js
const Websocket = require('ws')
const wss = new Websocket.Server({ port: 3000 })
const group = {}
const heartBeatTime = 25000
wss.on('connection'.function (ws) {
ws.isAlive = true
ws.heartBeatTimeIntervalObj = -1
ws.heartBeatTimeoutObj = -1
// Monitor the heartbeat timer. If there is no heartbeat for a long time, the connection will be disconnected automatically
const setHeartBeatTimeout = () = > {
if(ws.heartBeatTimeoutObj ! = = -1) {
clearTimeout(ws.heartBeatTimeoutObj)
ws.heartBeatTimeoutObj = -1
}
ws.heartBeatTimeoutObj = setTimeout(() = > {
ws.close()
ws.isAlive = false
}, heartBeatTime)
}
setHeartBeatTimeout()
ws.on('message'.function (message) {
console.log('server receive message: ', message.toString())
const data = JSON.parse(message.toString())
setHeartBeatTimeout()
if (data.event === 'login') {
ws.enterInfo = data
}
if (data.event === 'heartBeat' && data.content === 'ping') {
ws.isAlive = true
clearTimeout(ws.heartBeatTimeoutObj)
ws.heartBeatTimeoutObj = -1
ws.send(JSON.stringify({
event: 'heartBeat'.content: 'pong',}))return
}
if (typeof ws.roomId === 'undefined' && data.roomId) {
ws.roomId = data.roomId
if (typeof group[ws.roomId] === 'undefined') {
group[ws.roomId] = 1
} else {
group[ws.roomId]++
}
}
data.num = group[ws.roomId]
wss.clients.forEach(client= > {
if (client.readyState === Websocket.OPEN && client.roomId === ws.roomId) {
client.send(JSON.stringify(data))
}
})
})
ws.on('close'.function (message) {
group[ws.roomId]--
wss.clients.forEach(function each (client) {
if(client ! == ws && client.readyState === Websocket.OPEN && client.roomId === ws.roomId) { client.send(JSON.stringify({ ... ws.enterInfo,event: 'logout'.num: group[ws.roomId],
}))
}
})
})
})
Copy the code
Reference documentation
Book: The Definitive GUIDE to HTML5 WebSocket
HTTP, socket, Websocket connection and distinction: www.cnblogs.com/aspirant/p/…
Long connection heartbeat and reconnection design: juejin.cn/post/684490…
Load balancing: juejin.cn/post/684490…
Juejin. Cn/post / 684490…
Websocket tutorial: juejin.cn/post/684490…
Full duplex and half duplex: blog.csdn.net/a3192048/ar…
Websocket tutorial: juejin.cn/post/684490… , www.jmjc.tech/less/114
Websocket write chat room: www.liaoxuefeng.com/wiki/102291…
Ws dead simple: github.com/websockets/…
Summary of the websocket handshake: blog.csdn.net/QQ729533020…
Using.net and build websocket vue chat rooms: qinyuanpei. Making. IO/posts / 19896…