WebSocket 是一种协议,2008年被首次提出,2011年被广泛应用,并且大部分浏览器均支持此协议。
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。其实现了服务器和浏览器之间互相通信功能,在WebSocket 出现之前,解决实时获取服务端信息,一般使用 轮询 或者 长轮询 方案。这两种方案 无疑都造成了 服务器压力,WebSocket 就是为了解决这种问题而出现的。
WebSocket 能够替代 轮询 方案的优势在哪?
{
"sendid": 1,
"content": "hello"
}
轮询: 每隔固定的时间向服务器发送请求,在一个 http 协议 下的请求中,我们要发送如上的信息给服务端,我们将数据写在 请求体中,但是请求发送的数据不只是这些,远远不止。其包含了一系列的 http 协议下的数据和规则。
长轮询:浏览器发送请求后,一直等待响应,等服务器有了响应结果后,响应给浏览器,再关闭本次请求。这种方案,造成一个请求响应时长过长,并且若运用于浏览器和服务端多次通信,会极大的造成服务器压力。
WebSocket:
1. 实现了服务端主动实时跟浏览器通讯。
2. 浏览器仅需发送一次请求头中的规则和数据,降低了服务器运行的压力。
npm i express-ws
const express = require("express")
const app = express()
const ws = require("express-ws")
ws(app) // 将 ws 混入到 app中,模块化开发时也可以混入在 Router 对象中
app.listen(2345,()=>{
console.log("scoket 服务已开启")
})
app.ws("/socket", (ws,req)=>{
ws.on("close",()=>{ console.log("本次连接已关闭") })
ws.on("message", (msg)=>{ ws.send(msg) })
})
// 由此便完成了一个最简单的 WebSocket 接口
/**
* TODO: 鉴权思路
* 1. 用户登录成功后,给其返回一个 token 字符串
* 2. 前端通过 sec-websocket-protocol 这项子协议 携带 token 访问
* 3. 通过 用户IP 匹配 token
*/
const tokenCache= [{
ip: :ffff:10.10.10.10
tokenArr: ["88888888888888888"]
}] // 在这里我直接通过数组模拟已存在的 token 信息。用户 ip 可通过 req.ip 获取,一个 ip 下可能有多个 token
app.ws("/socket", (ws,req)=>{
ws.on("close",()=>{ console.log("本次连接已关闭") })
let islogin = false
tokenCache.map(ele=>{
if(ele.ip == req.ip){
ele.tokenArr.indexOf(req.headers['sec-websocket-protocol']) != -1? islogin = true: ''
}
return ele;
})
if(!islogin){
ws.send(JSON.stringify({ code: 1005, message:"身份认证无效或已过期" }))
ws.close();
return
}
ws.on("message", (msg)=>{ ws.send(msg) })
})
/**
* TODO: 鉴权思路
* 1. 对每个 websocket 连接,建立一个标识 sec-websocket-key
* 2. 指定标识进行通讯
* 3. 场景,网页版淘宝客服聊天。
*/
var keys = [] // 我在这里自定了一个 连接组,也可以使用 express-ws 模块提供的 getWss 函数
app.ws("/socket", (ws,req)=>{
ws.on("close",()=>{
keys = keys.filter(ele=>{
return ele.key != req.headers['sec-websocket-key']
})
})
let islogin = false
tokenCache.map(ele=>{
if(ele.ip == req.ip){
ele.tokenArr.indexOf(req.headers['sec-websocket-protocol']) != -1? islogin = true: ''
}
return ele;
})
if(!islogin){
ws.send(JSON.stringify({ code: 1005, message:"身份认证无效或已过期" }))
ws.close();
return
}
else keys.push({ key: req.headers['sec-websocket-key'], ws})
ws.on("message", (msg)=>{
ws.send(JSON.stringify({ code: 1002, message:"服务端已收到消息" }))
msg = JSON.parse(msg)
if(msg.close){ ws.close(); return }
keys.map(ele=>{
if(ele.key != req.headers['sec-websocket-key']){
ele.ws.send(JSON.stringify({ code: 1002, message:"来自其他人的消息" }))
}
})
return ele
})
})
到此,就实现了一个简化的 WebSocket 服务,前端我这边使用 vue2 版本进行示例。
data(){
return{
path:"ws://localhost:2345/socket",
socket: null
}
},
methods: {
init(){
if(typeof(WebSocket) === 'undefined'){ console.error('您的浏览器不支持WebSocket'); return ;}
// 实例化socket
this.socket = new WebSocket(this.path,sessionStorage.getItem("kifet"))
// 监听socket连接
this.socket.onopen = this.open
// 监听socket错误信息
this.socket.onerror = this.error
// 监听socket消息
this.socket.onmessage = this.getMessage
},
open(){
let parmas = JSON.stringify({ message: "你好服务器" })
this.socket.send(parmas)
},
error(){ console.log("socket连接错误") },
getMessage(value){ console.log(JSON.parse(value.data)) },
send() {
let parmas = JSON.stringify({ sendid: 1 ,content: "价格上能否优惠" })
this.socket.send(parmas)
},
close() {
let parmas = JSON.stringify({ close: true })
this.socket.send(parmas)
},
},
destroyed () {
// 销毁监听
this.socket.onclose = this.close
},
mounted(){
if(this.socket == null) this.init()
}
其他使用说明: