# 1、ws 库
安装
npm i ws
官方文档
https://github.com/websockets/ws
使用
- http 协议
访问 http://127.0.0.1:3000 即可
const express = require('express')
const app = express()
// 访问静态页面
app.use(express.static('./public'))
// 响应数据
app.get('/', (req, res) => {
res.send({ ok: 1 })
})
app.listen(3000)
- Websocket 协议
<!-- 一、前端 -->
<body>
<div>聊天室</div>
<script>
var ws = new WebSocket('ws://127.0.0.1:8080?token=&(localStorage.getItem("token"))')
ws.onopen = () => {
console.log('连接成功')
}
// 接收数据✨
ws.onmessage = (msgObj) => {
console.log('服务端发送的数据:', msgObj.data)
}
ws.onerror = () => {
console.log('error')
}
</script>
</body>
# 浏览器控制台
ws
# 客户端发送数据👀
ws.send('获取聊天室实时数据')
// 二、后端
const WebSocket = require('ws')
const WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({ port: 8080 })
wss.on('connection', function connection(ws, req) {
// token验证
const myURL = new URL(req.url, 'http://127.0.0.1:3000')
const payload = JWT.verify(myURL.searchParams.get('token'))
// 接收数据👀
ws.on('message', function message(data) {
// ① 普通方式
console.log('客户端发送的数据:%s', data)
// ② 群发方式🚩(可用多标签页或客户端模拟)
wss.clients.forEach(function each(client) {
// 不包括自己(若要包括自己:删除client !== ws条件即可)
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data, { binary: false })
}
})
})
// 服务端发送数据✨
if (payload) {
ws.send('欢迎来到聊天室!')
} else {
ws.send('未授权')
}
})
- ws.send 优化
<!-- 一、前端 -->
<body>
<div>聊天室</div>
<script>
// 接收数据✨
ws.onmessage = (msgObj) => {
msgObj = JSON.parse(msgObj.data)
switch (msgObj.type) {
case 0:
localStorage.removeItem('token')
location.href = '/login'
break
case 2:
console.log(msgObj)
break
}
}
ws.onerror = () => {
console.log('error')
}
</script>
</body>
// 二、后端
const WebSocketType = {
Error: 0, // 错误
GroupList: 1, // 获取列表
GroupChat: 2, // 群聊
SingleChat: 3 // 私聊
}
function createMsg(type, user, data) {
return JSON.stringify({
type,
user,
data
})
}
wss.on('connection', function connection(ws, req) {
// 服务端发送数据✨
if (payload) {
ws.send(createMsg(WebSocketType.GroupChat, null, '欢迎来到聊天室!'))
} else {
ws.send(createMsg(WebSocketType.Error, null, 'token过期'))
}
})
上面大概描述了 WebSocket 的使用情况,下面我们将要完整的构造一个聊天室。
- 前端
<!-- 一、前端 -->
<body>
<div>聊天室</div>
<script>
// 接收数据✨
ws.onmessage = (msgObj) => {
msgObj = JSON.parse(msgObj.data)
switch (msgObj.type) {
case 0:
localStorage.removeItem('token')
location.href = '/login'
break
case 1:
// 打印在线用户信息
console.log(JSON.parse(msgObj.data))
const onelineList = JSON.parse(msgObj.data)
// 🎈 在线用户列表渲染
spanCycle.innerHTML = ''
spanCycle.innerHTML = onelineList.map(
(item) => `
<span>${item.username}</span>
`
)
break
case 2:
// 🎈 打印在线群聊信息
var chatTitle = msgObj.user ? msgObj.user.username : '公告'
console.log(chatTitle + ':' + msgObj.data)
break
case 3:
// 🎈 打印在线私聊信息
console.log(msgObj.user.username + ':' + msgObj.data)
break
}
}
ws.onerror = () => {
console.log('error')
}
// 编写🚩聊天信息
sendButton.onclick = () => {
if (selectOption.value === 'all') {
// ① 群发
ws.send(JSON.stringify({ type: 2, data: inputTextID.value }))
} else {
// ② 私发
ws.send(
JSON.stringify({
type: 3,
data: inputTextID.value,
to: selectOption.value
})
)
}
}
</script>
</body>
# 浏览器控制台
ws
# 客户端发送数据👀
ws.send(JSON.stringify({type: 1}))
- 后端
// 二、后端
const WebSocket = require('ws')
const WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({ port: 8080 })
// 获取在线🚩用户信息封装
function sendUserList() {
const userList = Array.from(wss.clients).map((item) => item.user)
ws.send(createMsg(WebSocketType.GroupList, null, userList))
}
wss.on('connection', function connection(ws, req) {
// token验证
const myURL = new URL(req.url, 'http://127.0.0.1:3000')
const payload = JWT.verify(myURL.searchParams.get('token'))
// 发送数据
if (payload) {
ws.send(createMsg(WebSocketType.GroupChat, null, '欢迎来到聊天室!'))
// 1、为ws对象添加user属性(存储🤔用户信息)
ws.user = payload
// 2.2、发送在线用户信息【登录状态时】(群发方式,包括自己)
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
sendUserList()
}
})
} else {
ws.send(createMsg(WebSocketType.Error, null, 'token过期'))
}
// 接收数据
ws.on('message', function message(data) {
const msgObj = JSON.parse(data)
switch (msgObj.type) {
case WebSocketType.GroupList:
// 2.1、发送在线用户信息【前端请求时】(wss.clients结果为set🤙结构)
sendUserList()
break
case WebSocketType.GroupChat:
// ① 群发 —— 群聊信息
// console.log(msgObj.data)
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(createMsg(WebSocketType.GroupChat, ws.user, msgObj.data), { binary: false })
}
})
break
case WebSocketType.SingleChat:
// ② 私发 —— 群聊信息
// console.log(msgObj.data)
wss.clients.forEach(function each(client) {
if (client.user.username === msgObj.to && client.readyState === WebSocket.OPEN) {
client.send(createMsg(WebSocketType.SingleChat, ws.user, msgObj.data), {
binary: false
})
}
})
break
}
})
ws.on('close', () => {
// set结构删除🤙操作(删除离线用户)
wss.clients.delete(ws.user)
// 2.3、发送在线用户信息【退出登录时】(实时更新)
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
sendUserList()
}
})
})
})
# 2、socket.io
安装
npm i socket.io
官方文档
https://github.com/socketio/socket.io
① 对比
前端的 socket.on()
和 后端的 socket.emit()
类似于 vue 中的数据共享。并且还有特性:
- socket.emit 是支持发送对象,无需转换为字符串
- ws 复杂的 case 方式,替换为了简洁的 socket.on 方式
- 能够自动的降级处理
- 能够断连时自动重连
- ……
② 聊天室案例改造(express 中使用)
- 前端
<!-- 一、前端 -->
<head>
<!-- 引入socket.io的客户端代码 -->
<script src="https://github.com/socketio/socket.io/tree/main/client-dist/socket.io.min.js"></script>
</head>
<body>
<div>聊天室</div>
<script>
const socket = io('ws://127.0.0.1:8080?token=&(localStorage.getItem("token"))')
// 打印公告
socket.on(2, (msgObj) => {
var chatTitle = msgObj.user ? msgObj.user.user : '公告'
console.log(chatTitle + ':' + msgObj.data)
})
// 打印错误
socket.on(0, (msgObj) => {
localStorage.removeItem('token')
location.href = '/login'
})
// 打印在线用户信息
socket.on(1, (msgObj) => {
const onelineList = msgObj.data
// 🎈 在线用户列表渲染
spanCycle.innerHTML = ''
spanCycle.innerHTML = onelineList.map(
(item) => `
<span>${item.username}</span>
`
)
})
socket.on(2, (msgObj) => {
// 🎈 打印在线群聊信息
var chatTitle = msgObj.user ? msgObj.user.username : '公告'
console.log(chatTitle + ':' + msgObj.data)
})
socket.on(3, (msgObj) => {
// 🎈 打印在线私聊信息
console.log(msgObj.user.username + ':' + msgObj.data)
})
// 编写🚩聊天信息
sendButton.onclick = () => {
if (selectOption.value === 'all') {
// ① 群发
socket.emit(2, { data: inputTextID.value })
} else {
// ② 私发
socket.emit(3, {
data: inputTextID.value,
to: selectOption.value
})
}
}
</script>
</body>
# 浏览器控制台
io
# 客户端发送数据👀
io.emit({type: 1})
- 后端
// 二、后端
const app = require('express')()
const server = require('http').createServer(app)
const io = require('socket.io')(server)
// 获取在线🚩用户信息封装
function emitUserList(io) {
// const userList = Array.from(is.sockets.sockets).map((item) => item[1].user)
// undefined优化
const userList = Array.from(is.sockets.sockets)
.map((item) => item[1].user)
.filter((item) => item)
io.sockets.emit(WebSocketType.GroupList, createMsg(null, userList))
}
io.on('connection', (socket, req) => {
// token验证
// 发现已经帮我们解析👏
// console.log(socket.handshake.query.token)
const payload = JWT.verify(socket.handshake.query.token)
// 发送数据
if (payload) {
socket.emit(WebSocketType.GroupChat, createMsg(null, '欢迎来到聊天室!'))
// 1、为ws对象添加user属性(存储🤔用户信息)
socket.user = payload
// 2.2、发送在线用户信息【登录状态时】(群发方式,io.sockets.emit)
emitUserList(io)
} else {
socket.emit(WebSocketType.Error, createMsg(null, 'token过期'))
}
socket.on(WebSocketType.GroupList, () => {
// console.log(is.sockets.sockets)
// 2.1、发送在线用户信息【前端请求时】(is.sockets.sockets结果为map🤙结构)
emitUserList(io)
})
// ① 群发 —— 群聊信息
socket.on(WebSocketType.GroupChat, (msg) => {
// console.log(msg)
// 包含自己(io.sockets.emit)
io.sockets.emit(WebSocketType.GroupChat, createMsg(socket.user, msg.data))
// 不包含自己(sockets.broadcast.emit)
})
// ② 私发 —— 群聊信息
socket.on(WebSocketType.GroupList, (msgObj) => {
Array.from(is.sockets.sockets).forEach((item) => {
if (item[1].user.username === msgObj.to) {
// 私发🍗
item[1].emit(WebSocketType.GroupChat, createMsg(socket.user, msgObj.data))
}
})
})
socket.on('close', () => {
// 2.3、发送在线用户信息【退出登录时】(实时更新)
emitUserList(io)
})
})
server.listen(3000)