WebSocket 是什么
WebSocket 是一种在客户端与服务器之间保持TCP长连接的网络协议,这样它们就可以随时进行信息交换。提供了一个双向通讯的功能。
WebSocket 解决了什么
WebSocket 解决了传统的 Ajax 只能单向通讯的问题
WebSocket 的使用
【客户端】
const ws = new WebSocket('ws://localhost:6050', 'echo-protocol')
ws.onopen = () => {
this.send('connect success')
}
ws.onmessage = (event) => {
console.log(event, 'event')
}
【服务端】
可以参考 npm version](badge.fury.io/js/websocke…)
WebSocket 的封装
WebSocket 封装的考虑点
- 以类的方式封装
- 异常情况下的断开重连、用户手动断开则不重连
- 消息发送失败的处理,下次连接成功时发送之前失败的内容
- 订阅消息、取消订阅(需要结合发布订阅者模式)
- 根据不同的类型,处理不通的消息
- 销毁(这一步处理的不是很好、各位大佬有什么建议、可以教教小弟)
WebSocket 封装的实现
ws.js
class Ws {
// 要连接的URL
url
// 一个协议字符串或一个协议字符串数组。
// 这些字符串用来指定子协议,这样一个服务器就可以实现多个WebSocket子协议
protocols
// WebSocket 实例
ws
// 是否在重连中
isReconnectionLoading = false
// 延时重连的 id
timeId = null
// 是否是用户手动关闭连接
isCustomClose = false
// 错误消息队列
errorStack = []
// 消息管理中心
eventCenter = new EventCenter()
constructor(url, protocols) {
this.url = url
this.protocols = protocols
this.createWs()
}
createWs() {
if ('WebSocket' in window) {
// 实例化
this.ws = new WebSocket(this.url, this.protocols)
// 监听事件
this.onopen()
this.onerror()
this.onclose()
this.onmessage()
} else {
console.log('你的浏览器不支持 WebSocket')
}
}
// 监听成功
onopen() {
this.ws.onopen = () => {
console.log(this.ws, 'onopen')
// 发送成功连接之前所发送失败的消息
this.errorStack.forEach(message => {
this.send(message)
})
this.errorStack = []
this.isReconnectionLoading = false
}
}
// 监听错误
onerror() {
this.ws.onerror = (err) => {
console.log(err, 'onerror')
this.reconnection()
this.isReconnectionLoading = false
}
}
// 监听关闭
onclose() {
this.ws.onclose = () => {
console.log('onclose')
// 用户手动关闭的不重连
if (this.isCustomClose) return
this.reconnection()
this.isReconnectionLoading = false
}
}
// 接收 WebSocket 消息
async onmessage() {
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
this.eventCenter.emit(data.type, data.data)
} catch (error) {
console.log(error, 'error')
}
}
}
// 重连
reconnection() {
// 防止重复
if (this.isReconnectionLoading) return
this.isReconnectionLoading = true
clearTimeout(this.timeId)
this.timeId = setTimeout(() => {
this.createWs()
}, 3000)
}
// 发送消息
send(message) {
// 连接失败时的处理
if (this.ws.readyState !== 1) {
this.errorStack.push(message)
return
}
this.ws.send(message)
}
// 手动关闭
close() {
this.isCustomClose = true
this.ws.close()
}
// 手动开启
start() {
this.isCustomClose = false
this.reconnection()
}
// 订阅
subscribe(eventName, cb) {
this.eventCenter.on(eventName, cb)
}
// 取消订阅
unsubscribe(eventName, cb) {
this.eventCenter.off(eventName, cb)
}
// 销毁
destroy() {
this.close()
this.ws = null
this.errorStack = null
this.eventCenter = null
}
}
事件管理中心(发布订阅者模式)
eventCenter.js
class EventCenter {
// 通过事件类型作为属性来管理不通的事件回调
eventStack = {}
constructor() {
this.eventStack = {}
}
on(eventName, cb) {
const { eventStack } = this
const eventValue = eventStack[eventName]
eventValue ? eventValue.push(cb) : eventStack[eventName] = [cb]
}
once(eventName, cb) {
const { eventStack } = this
const eventValue = eventStack[eventName]
// 利用闭包的形式 来模拟一次性监听的功能函数
const tempCb = () => {
let isOutOfDate = false
return () => {
if (isOutOfDate) return
cb()
isOutOfDate = true
}
}
eventValue ? eventValue.push(tempCb()) : eventStack[eventName] = [tempCb()]
}
off(eventName, cb) {
const { eventStack } = this
const eventValue = eventStack[eventName]
if (!eventValue) return
(eventValue || []).forEach((eventCb, index) => {
if (eventCb === cb) {
eventValue.splice(index, 1)
}
})
}
emit(eventName, data) {
const { eventStack } = this
const eventValue = eventStack[eventName]
if (!eventValue) return
(eventValue || []).forEach(eventCb => {
eventCb(data)
})
}
}
使用
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{ msg }}
<button @click="handleClose">close</button>
<button @click="handleStart">start</button>
<button @click="handleUnsubscribe">unsubscribe</button>
<button @click="handleDestroy">destroy</button>
</div>
<script src="./eventCenter.js"></script>
<script src="./ws.js"></script>
<script>
// 第一个连接
const WS = new Ws('ws://localhost:6050', 'echo-protocol')
WS.send('conent success')
const handleChat = data => console.log(data)
WS.subscribe('chat', handleChat)
// 第二个连接
const WS2 = new Ws('ws://localhost:6050', 'echo-protocol')
WS2.send('conent success')
const handleChat2 = data => console.log(data)
WS2.subscribe('chat', handleChat2)
let count = 0
let timeId = setInterval(() => {
++count
WS.send(`count: ${count}`)
}, 1000)
let count2 = 0
let timeId2 = setInterval(() => {
++count2
WS2.send(`count2: ${count2}`)
if (count2 === 20) {
WS2.unsubscribe('chat', handleChat2)
}
}, 1000)
const APP = new Vue({
el: '#app',
data: {
msg: '...'
},
methods: {
handleClose() {
clearInterval(timeId)
WS.close()
},
handleStart() {
WS.start()
},
handleUnsubscribe() {
WS.unsubscribe('chat', handleChat)
},
handleDestroy() {
clearInterval(timeId)
WS.destroy()
}
}
})
</script>
</body>
</html>
服务端(没深入,复制的官方代码)
serve.js
const http = require('http')
var WebSocketServer = require('websocket').server
const app = http.createServer((req, res) => {
console.log('...')
res.setHeader("Content-type","application/json")
res.end('success')
})
app.listen(6050, () => {
console.log('listen port 6050')
})
const wsServer = new WebSocketServer({
httpServer: app,
// You should not use autoAcceptConnections for production
// applications, as it defeats all standard cross-origin protection
// facilities built into the protocol and the browser. You should
// *always* verify the connection's origin and decide whether or not
// to accept it.
autoAcceptConnections: false
});
function originIsAllowed(origin) {
// put logic here to detect whether the specified origin is allowed.
return true;
}
wsServer.on('request', function(request) {
if (!originIsAllowed(request.origin)) {
// Make sure we only accept requests from an allowed origin
request.reject();
console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
return;
}
var connection = request.accept('echo-protocol', request.origin);
console.log((new Date()) + ' Connection accepted.');
connection.on('message', function(message) {
if (message.type === 'utf8') {
console.log('Received Message: ' + message.utf8Data);
connection.sendUTF(JSON.stringify({
type: 'chat',
data: message.utf8Data
}));
}
else if (message.type === 'binary') {
console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');
connection.sendBytes(message.binaryData);
}
});
connection.on('close', function(reasonCode, description) {
console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
});
});
博文推荐
- 笔记:Vue 常见面试题汇总及解析
- Vue3.0 中 Object.defineProperty 的代替方案 Proxy
- vue 3.0 —— 之初体验一
- 一张图搞懂原型、原型对象、原型链
- Promise 原理篇 = 从 0 到 1 构建一个 Promise
【笔记不易,如对您有帮助,请点赞,谢谢】
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!