Node 后端
初始化
npm init -y
安装依赖
npm i ws
创建 index.js
在根目录下创建 index.js 文件
创建好后找到 package.json 文件
在 scripts 里加入启动命令 我们就可以通过 npm run serve 启动服务了
"scripts": {
"serve": "node index.js",
},
编写 websocket 服务端代码
const { WebSocketServer } = require("ws");
const WebSocket = require("ws");
const onlineList = [];
// 我们的 port 是 8888
const wss = new WebSocketServer({ port: 8888 });
// 如果有ws 就表明我们已经初始化成功
if (wss) {
console.log("websocket Initialized successfully on port: " + 8888);
}
wss.on("connection", function connection(ws, request) {
let welCome = JSON.stringify({
type: "tips",
content: "欢迎加入聊天室,开始畅聊吧~",
});
// 初始化时发送欢迎的消息
ws.send(welCome, { binary: false }); // binary 表示是否是文件
ws.on("error", console.error);
// 注册收到消息的事件
ws.on("message", async function message(data) {
const message = JSON.parse(data);
switch (message.type) {
// 用户在连接成功以后会发送消息 我们会在此获取用户信息
case "init":
if (message.userId) {
// 为当前用户的 ws连接绑定 用户id 用于用户断开链接时 改变用户在线状态
ws.userId = message.userId;
// 上线
keepLatestOnlineList("online", message);
}
break;
// 群发消息
case "message":
wss.clients.forEach(function each(client) {
// readyState、WebSocket.OPEN 他们都是 1 表示当前链接是打开状态
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message), { binary: false });
}
});
break;
default:
break;
}
});
// 被动断开 说明用户已经离开网站了 我们维护在线列表
ws.on("close", function () {
keepLatestOnlineList("close", { userId: ws.userId });
});
});
function keepLatestOnlineList(type, message) {
let index = onlineList.findIndex((item) => item.userId === message.userId);
switch (type) {
// 根据消息类型对消息进行分类处理
case "online":
if (index === -1) {
onlineList.push({
userId: message.userId,
});
}
console.log(message.nickname + " 上线了...");
break;
case "close":
if (index !== -1) {
onlineList.splice(index, 1);
console.log("有用户断开连接...");
}
break;
default:
break;
}
// 群发在线人数
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
let message = JSON.stringify({
type: "onlineList",
list: onlineList,
});
client.send(message, { binary: false });
}
});
}
Vite+Vue3 前端
初始化 vue 项目
npm init vite@latest
初始化好以后 进入项目
cd vite-project
安装依赖
npm i
运行项目
npm run dev
编写组件和样式
需要先把 App 页面的 内容清空
<script setup>
import { ref, onMounted } from "vue";
const userInfo = ref({
userId: "",
});
const yourMessage = ref("");
const messageList = ref([]);
const onlineList = ref([]);
let ws = null;
const sendMessage = () => {
if (yourMessage.value) {
ws.send(
JSON.stringify({
type: "message",
userId: userInfo.value.userId,
content: yourMessage.value,
})
);
yourMessage.value = "";
}
};
const initWebsocket = () => {
ws = new WebSocket("ws://localhost:8888/");
console.log(ws);
ws.onopen = () => {
console.log("websocket连接成功");
ws.send(
JSON.stringify({
type: "init",
userId: userInfo.value.userId,
content: "欢迎来到聊天室",
})
);
};
ws.onerror = () => {
console.log("websocket连接失败");
};
ws.onmessage = (data) => {
const message = JSON.parse(data.data);
switch (message.type) {
case "tips":
console.log(message.content);
break;
case "message":
messageList.value.push(message);
break;
case "onlineList":
onlineList.value = message.list;
break;
default:
break;
}
};
ws.onclose = () => {
console.log("websocket连接关闭");
};
};
onMounted(() => {
userInfo.value.userId = new Date().getTime().toString().slice(8);
initWebsocket();
});
</script>
<template>
<div class="app">
<div class="chat-container">
在线人数{{ onlineList.length }}
<div class="chat-room">
<div class="message-item" v-for="(item, index) in messageList" :key="index">
<div class="flex-left" v-if="item.userId !== userInfo.userId">
<div class="avatar">
{{ "W" + item.userId }}
</div>
<div style="margin-left: 10px" class="message-content">
{{ item.content }}
</div>
</div>
<div v-else class="flex-right">
<div class="message-content" style="margin-right: 10px">
{{ item.content }}
</div>
<div class="avatar">
{{ "W" + item.userId }}
</div>
</div>
</div>
</div>
<div class="send-box">
<input class="message-input" type="text" v-model="yourMessage" @keyup.enter="sendMessage" />
<button class="send-btn" @click="sendMessage">发送</button>
</div>
</div>
</div>
</template>
<style scoped>
.app {
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: #fff;
display: grid;
place-items: center;
}
.chat-container {
width: 600px;
height: 600px;
overflow: hidden;
background: #eae3e3;
border-radius: 8px;
}
.chat-room {
height: calc(100% - 100px);
padding: 10px;
box-sizing: border-box;
overflow: auto;
background: #ecd5d5;
}
.message-item {
width: 80%;
margin: 0 auto;
margin-bottom: 10px;
}
.flex-left {
display: flex;
justify-content: flex-start;
}
.flex-right {
display: flex;
justify-content: flex-end;
}
.avatar {
width: 40px;
height: 40px;
line-height: 40px;
border-radius: 50%;
overflow: hidden;
background: #fafafa;
}
.message-content {
min-height: 30px;
background: #fff;
border-radius: 8px;
padding: 10px;
}
.send-box {
height: 80px;
display: flex;
align-items: center;
}
.message-input {
height: 60px;
flex: 1;
}
.send-btn {
width: 150px;
height: 60px;
line-height: 60px;
text-align: center;
}
</style>
云部署
云部署就是将 Websocket 的连接地址改成云服务器的地址 并且带上后缀
比如下面的地址就是带了 ws 后缀的
比如 new WebSocket(“ws://example.com/ws/”);
这样写以后 websocket 访问的是我们的 服务器的 80 端口 我们需要使用 nginx 配置端口映射
ws 应该对应的端口映射是
server {
listen 80;
server_name localhost;
location /ws/ {
proxy_pass http://ip地址:8888/; # ip 地址就是服务器地址 也可以改成 域名 8888 就是你的服务器端口号
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}