文章目录
web Socket介绍
-
http请求:
客户端发送请求,服务端才会响应请求。
-
web Socket请求
WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。
webSocket的应用场景:
● 弹幕
● 媒体聊天
● 协同编辑
● 基于位置的应用
● 体育实况更新
● 股票基金报价实时更新
创建WebSocket连接
WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:
GET ws://1oca1host:3000/ws/chat HTTP/1.1
Host: localhost
upgrade: websocket
Connection: Upgrade
origin: http://localhost:3000
Sec-WebSocket-Key:client-random-string
Sec-Websocket-Version: 13
该请求和普通的HTTP请求有几点不同:
- GET请求的地址不是类似/path/,而是以
ws://
开头的地址; - 请求头
upgrade: websocket
和Connection: upgrade
表示这个连接将要被转换为WebSocket连接; - Sec-websocket-Key 是用于标识这个连接,并非用于加密数据;
- Sec-websocket-version指定了WebSocket的协议版本。
随后,服务器如果接受该请求,就会返回如下响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-Websocket-Accept:server-random-string
该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket
指定的WebSocket协议。
这样,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本, 一种是二进制数据。通常,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。
Node实现WebSocket模块
ws模块
下载ws模块:npm i ws
简单实现聊天功能
前端创建webSocket协议
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
Chat
<h1>聊天室</h1>
<script>
// WebSocket()是基于浏览器的内置函数
// 建立连接
var ws = new WebSocket("ws://localhost:8080")
ws.onopen=()=>{
console.log("连接成功")
}
ws.onmessage=(msgObj)=>{
console.log(msgObj)
}
ws.onerror=()=>{
console.log("error")
}
</script>
</body>
</html>
node创建webSocket服务器
const express = require("express")
const app = express()
// http响应
app.use(express.static("./public"))
app.get("/",(req, res)=> {
res.send({ok:1})
})
app.listen(3000)
// webSocket响应
const WebSocket = require("ws")
const WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({ port: 8080 });
// ws表示当前连接的客户端,wss表示当前创建的服务器
wss.on('connection', function connection(ws) {
// 将数据从服务端发送到客户端
ws.send('欢迎来到聊天室');
// ws.send不会直接显示在页面上,在data数据中
// 接收从客户端传过来的消息
ws.on('message', function message(data) {
console.log('received: %s', data);
// 转发给其他客户端,遍历客户端给每个人发消息,并且排除自己
wss.clients.forEach(function each(client) {
// 判断当前客户端是否处于连接状态
if (client!==ws&&client.readyState === WebSocket.OPEN) {
client.send(data,{binary:false});
}
});
});
});
实现效果:
(一个网页相当于一个用户)
QQ录屏20220711165339
实现登录鉴权和身份识别
chat.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
Chat
<h1>聊天室</h1>
<input type="text" id="text" /><button id="send">send</button>
<hr>
<div id="chats"></div>
<hr>
私聊
<div id="s_chats"></div>
<hr>
<h3>用户列表</h3>
<select id="select"></select>
<h3></h3>
<script>
const WebSocketType = {
Error: 0,
GroupList: 1,
GroupChat: 2,
SingleChat: 3,
};
// 建立socket链接,带着token
var chats = document.getElementById("chats");
var s_chats = document.getElementById("s_chats");
var select = document.getElementById("select");
var text = document.getElementById("text");
var send = document.getElementById("send");
// WebSocket()是基于浏览器的内置函数
// 建立连接
var ws = new WebSocket(
`ws://localhost:8080?token=${localStorage.getItem("token")}`
);
// 接收从服务端传过来的数据
ws.onopen = () => {
console.log("连接成功");
};
ws.onmessage = (msgObj) => {
// console.log(msgObj.data);
msgObj = JSON.parse(msgObj.data);
switch (msgObj.type) {
case WebSocketType.Error:
localStorage.removeItem("token");
location.href = "/login";
break;
case WebSocketType.GroupList:
const onlineList = JSON.parse(msgObj.data);
select.innerHTML = "";
select.innerHTML =
`<option value="all">all</option>` +
onlineList.map(
(item) => `
<option value="${item.username}">
${item.username}
</option>`
)
.join("");
// console.log(JSON.parse(msgObj.data))
break;
case WebSocketType.GroupChat:
console.log(msgObj);
console.log(msgObj.user?.username);
var h_html = document.createElement("h3")
h_html.innerHTML = `${msgObj.user?.username??""}:${msgObj.data}`;
chats.appendChild(h_html)
break;
case WebSocketType.SingleChat:
console.log(msgObj);
console.log(msgObj.user?.username);
var h_html = document.createElement("h3")
h_html.innerHTML = `${msgObj.user?.username??""}:${msgObj.data}`;
s_chats.appendChild(h_html)
break;
}
};
ws.onerror = () => {
console.log("error");
};
send.onclick = () => {
if (select.value === "all") {
// console.log("群发")
ws.send(createMessage(WebSocketType.GroupChat, text.value));
} else {
// console.log("私聊")
ws.send(createMessage(WebSocketType.SingleChat, text.value,select.value));
}
};
function createMessage(type, data,to) {
return JSON.stringify({
type,
data,
to
});
}
</script>
</body>
</html>
webSocket.js
const JWT = require("../utils/jwt")
// webSocket响应
const WebSocket = require("ws")
const WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({ port: 8080 });
// ws表示当前连接的客户端,wss表示当前创建的服务器
wss.on('connection', function connection(ws, req) {
// 获取请求,检查token
const myURL = new URL(req.url, "http://127.0.0.1:3000")
// console.log(myURL.searchParams.get("token"))
// 校验token
const payload = JWT.verify(myURL.searchParams.get("token"))
if (payload) {
// 将数据从服务端发送到客户端
ws.send(createMessage(WebSocketType.GroupChat, null, '欢迎来到聊天室'));
ws.user = payload//将用户信息添加到ws中
// 只要有用户登录,就会告知所有用户
sendAll()
} else {
ws.send(createMessage(WebSocketType.Error,null,'未授权'));
}
// 接收从客户端传过来的消息
ws.on('message', function message(data) {
const msgObj = JSON.parse(data)
switch (msgObj.type) {
case WebSocketType.GroupList:
// 在线用户
// console.log(Array.from(wss.clients).map(item=>item.user))
ws.send(createMessage(WebSocketType.GroupList,null,JSON.stringify(Array.from(wss.clients).map(item=>item.user))))
break;
case WebSocketType.GroupChat:
// console.log(msgObj.data)
// 群发消息,显示所有用户
wss.clients.forEach(function each(client) {
if (client.readyState == WebSocket.OPEN) {
client.send(createMessage(WebSocketType.GroupChat, ws.user, msgObj.data), {binary: false})
}
})
break;
case WebSocketType.Error:
break;
case WebSocketType.SingleChat:
wss.clients.forEach(function each(client) {
if (client.user.username===msgObj.to && client.readyState == WebSocket.OPEN) {
client.send(createMessage(WebSocketType.SingleChat, ws.user, msgObj.data), {binary: false})
}
})
break;
}
});
// 等有人离开时,也重新更新用户列表
ws.on("close", () => {
wss.clients.delete(ws.user)
sendAll()
// console.log(ws.user)
})
});
const WebSocketType = {
Error: 0,
GroupList: 1,
GroupChat: 2,
SingleChat:3
}
function createMessage(type, user, data) {
return JSON.stringify({
type,
user,
data
})
}
function sendAll() {
// 群发消息,显示所有用户
wss.clients.forEach(function each(client) {
if (client.readyState == WebSocket.OPEN) {
client.send(createMessage(WebSocketType.GroupList,null,JSON.stringify(Array.from(wss.clients).map(item=>item.user))))
}
})
}
实现效果:
QQ录屏20220711225558
整个代码流程:
- 前端登录:发出scoket长连接 ⇒ 服务端
wss.on
接收链接 ⇒ 用户鉴权 ⇒ 成功就返回登陆成功的信息 - 前端群聊: 前端发出群聊信息 ⇒ 服务端接收群聊信息 ⇒ 转发给每个用户
- 前端私聊:前端发出私聊信息 ⇒ 服务端接受私聊信息 ⇒ 转发给指定用户
代码获取:
链接:https://pan.baidu.com/s/1NQxKeiSyWwvOC8WNUOUaog?pwd=xtfx
提取码:xtfx
小结
群发流程:
私聊流程:
使用ws编写的代码,前端通过 ws.onmessage=()=>{},后端通过 ws.on(“message”,()=>{})类似的方法进行前后端信息的传递。
方法固定,比较死板
socket.io模块
比ws模块更加灵活,兼容性也更好。
- 安装:
npm i socket.io
同时用户端有对应的js版本的socket.io
- 前后端通过相同的
事件名
进行通信 - 前后端都是通过
emit(触发)和on(监听)
- 不需要写switch判断聊天类型了,在
emit和on
的第一个参数直接指明类型。
eg:
实现聊天室功能:
- websocket.js
const { Socket } = require("socket.io");
const JWT = require("../utils/jwt")
// 服务端的io模块
function start(server) {
const io = require("socket.io")(server);
io.on("connection", (socket) => {
// token
// console.log("11111",socket.handshake.query.token)
// 判断token是否过期
const payload = JWT.verify(socket.handshake.query.token)
if (payload) {
socket.user = payload
// 发送欢迎
socket.emit(WebSocketType.GroupChat,createMessage(null,"欢迎来到聊天室"))
// 发送所有用户列表
sendAll(io)
} else {
socket.emit(WebSocketType.Error,createMessage(null,"token过期"))
}
socket.on(WebSocketType.GroupList, () => {
// // io.sockets.sockets代表所有客户端
// io.sockets.sockets是map结构,可以转化为数组结构(键的下标是0,值的下标是1)
console.log(Array.from(io.sockets.sockets).map(item =>item[1].user))
})
socket.on(WebSocketType.GroupChat, (msg) => {
// 群发消息用io.sockets
io.sockets.emit(WebSocketType.GroupChat, createMessage(socket.user, msg.data))
// 除了自己不发,给其他人发
// socket.broadcast.emit(WebSocketType.GroupChat, createMessage(socket.user, msg.data))
})
socket.on(WebSocketType.SingleChat, (msg) => {
// io.sockets.sockets代表所有客户端
Array.from(io.sockets.sockets).forEach(item => {
if (item[1].user.username === msg.to) {
item[1].emit(WebSocketType.SingleChat,createMessage(socket.user,msg.data))
}
})
})
// 离线后重新发送用户列表(固定写法)
socket.on("disconnect", () => {
sendAll(io)
})
})
}
const WebSocketType = {
Error: 0,
GroupList: 1,
GroupChat: 2,
SingleChat:3
}
function createMessage(user, data) {
return {
user,
data
}
}
function sendAll(io) {
// 群发消息,显示所有用户
io.sockets.emit(WebSocketType.GroupList,createMessage(null,Array.from(io.sockets.sockets).map(item =>item[1].user).filter(item=>item)))
}
module.exports = start
- chat.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- // 引入socket.io(js版本) -->
<script type='text/javascript' src='javascripts/socketio.js'></script>
</head>
<body>
Chat
<h1>聊天室</h1>
<h1>当前用户:
<b id="user"></b>
</h1>
<input type="text" id="text" /><button id="send">send</button>
<select id="select"></select>
<hr>
<div id="chats"></div>
<hr>
私聊
<div id="s_chats"></div>
<hr>
<h3></h3>
<script>
const WebSocketType = {
Error: 0,
GroupList: 1,
GroupChat: 2,
SingleChat: 3,
};
// 建立socket链接,带着token
var chats = document.getElementById("chats");
var s_chats = document.getElementById("s_chats");
var select = document.getElementById("select");
var text = document.getElementById("text");
var send = document.getElementById("send");
var user = document.getElementById("user")
user.innerHTML = localStorage.getItem("username")
// 引入socket.io(js端)
// 不传参时,默认传递3000端口,socketio端口默认和http的端口一致都是3000
// 这里进行修改
const socket = io(`ws://localhost:3000?token=${localStorage.getItem("token")}`)
// 前后端通过相同的事件名进行通信
socket.on(WebSocketType.GroupChat,(msg)=>{
var h_html = document.createElement("h3")
h_html.innerHTML = `${msg.user?.username??"广播"}:${msg.data}`;
chats.appendChild(h_html)
})
socket.on(WebSocketType.SingleChat,(msg)=>{
console.log(msg)
var h_html = document.createElement("h3")
h_html.innerHTML = `${msg.user?.username??"广播"}:${msg.data}`;
s_chats.appendChild(h_html)
})
socket.on(WebSocketType.Error,(msg)=>{
localStorage.removeItem("token")
location.href="/login"
})
socket.on(WebSocketType.GroupList,(msg)=>{
console.log(msg)
const onlineList = msg.data;
select.innerHTML = "";
select.innerHTML =
`<option value="all">all</option>` +
onlineList.map(
(item) => `
<option value="${item.username}">
${item.username}
</option>`
)
.join("");
})
send.onclick = () => {
if (select.value === "all") {
// console.log("群发")
socket.emit(WebSocketType.GroupChat,createMessage(text.value))
} else {
// console.log("私聊")
socket.emit(WebSocketType.SingleChat,createMessage(text.value,select.value))
}
};
function createMessage(data,to) {
// 使用socket可以直接传对象,无需转换成字符串
return {
data,
to
};
}
</script>
</body>
</html>