1. WebSocket (最主流)
-
原理 :它是 HTML5 的协议。首先通过 HTTP 进行一次"握手",然后升级协议为
ws(加密为wss)。一旦连接建立,就像打长途电话,双方可以随时说话,不用再挂断重拨。 -
优势:全双工、延迟极低。
2. SSE (Server-Sent Events)
-
原理:单向通道。服务器可以不断地往前端发流数据。
-
优势 :实现简单,自动重连,适合像 ChatGPT 那样的打字机效果。
3. 长轮询 (Polling)
🚩 总结
Vue3 实时会话 = Socket.io (或 MQTT.js) + Composition API (Hooks) + 后端 Node.js/Java 支
那这个socket.io要是发送图片呢?emo表情呢,还有的话是文档excel之类的呢?还有的心跳检测 ,消息去重与有序性,鉴权(在 query 或者是 auth 参数),到底怎么写呢?
哈哈哈。。。
-
原理:前端每隔 1 秒问一次:"有消息吗?"。
-
现状:已经过时,浪费流量和性能,除非要兼容极老旧的浏览器。
|----------------------------|-------------------------------------|-----------------------------------|
| 框架名称 | 特点 | 适用场景 |
| Socket.io | 王者级别。自带断线重连、心跳检测、房间管理。 | 复杂的 IM 聊天室、协同办公。 |
| Vue-Socket.io-Extended | 专门为 Vue 封装的插件,支持 Vue3 和 Pinia/Vuex。 | Vue3 项目快速集成。 |
| MQTT.js | 基于发布/订阅模式。 | 物联网 (IoT)、 ThingLinks 这种项目必备。 |
| GoEasy | 国内商业服务,极其稳定,三行代码上线。 | 懒人福音,不想折腾后端服务器的首选。 |
| | | |在 Vue3 中,我们通常把 WebSocket 逻辑封装在一个 Hook 里,方便多个页面复用。
1. 安装依赖
Bash
npm install socket.io-client2. 封装
useSocket.tsTypeScript
import { ref, onUnmounted } from 'vue'; import { io, Socket } from 'socket.io-client'; export function useSocket(url: String) { const socket = io(url); const messages = ref([]); const isConnected = ref(false); // 监听连接 socket.on('connect', () => { isConnected.value = true; console.log('连接成功:', socket.id); }); // 监听服务端发来的消息 socket.on('chat_message', (msg) => { messages.value.push(msg); }); // 发送消息的方法 const sendMessage = (text: string) => { socket.emit('chat_message', { text, time: new Date() }); }; // 组件卸载时断开连接,防止内存泄漏 onUnmounted(() => { socket.disconnect(); }); return { messages, isConnected, sendMessage }; }3. 页面使用
HTML
<template> <div class="chat-container"> <p v-if="!isConnected">正在连接服务器...</p> <div v-for="(item, index) in messages" :key="index"> {{ item.text }} </div> <input v-model="inputMsg" @keyup.enter="handleSend" /> <button @click="handleSend">发送</button> </div> </template> <script setup> import { ref } from 'vue'; import { useSocket } from '@/hooks/useSocket'; const inputMsg = ref(''); const { messages, isConnected, sendMessage } = useSocket('http://localhost:3000'); const handleSend = () => { if (inputMsg.value) { sendMessage(inputMsg.value); inputMsg.value = ''; } }; </script> -
心跳检测 (Heartbeat):必须每隔一段时间(如 30 秒)发个"乒乓包",确认对方还活着,否则连接会被运营商强行掐断。
-
消息去重与有序性 :在高并发下,消息可能会乱序,前端需要根据
msgId或timestamp进行排序。 -
安全性 :WebSocket 也要鉴权!建立连接时,记得在
query或者是auth参数里带上你的 Token。
在 Socket.io 中,发送复杂内容本质上只有两种路径:"发文件流" 或 "发 URL 地址"。
1. 发送图片与文档(主流推荐:URL 模式)
不要直接把几 MB 的图片或 Excel 转成 Base64 塞进 Socket 管道!这会导致消息拥塞,甚至让所有人的聊天卡顿。
标准流程:
上传:前端 Axios 接口将文件传到服务器(如 OSS、MinIO)。
获取 URL :服务器返回文件的访问地址(例如 http://cdn.com/a.xlsx)。
发消息:通过 Socket.io 发送一个包含 URL 和文件类型的 JSON 对象。
// 发送文件的消息结构
const fileMsg = {
type: 'file', // 或者 'image'
content: 'http://xxx.com/test.xlsx',
fileName: '2026年报表.xlsx',
fileSize: '1.2MB'
};
socket.emit('chat_message', fileMsg);
二、 鉴权(Auth & Query)
为了安全,不能让谁都能连上你的 Socket。通常在建立连接的那一刻就要验证身份。
JavaScript
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000', {
// 方式 1:auth 属性(推荐,更安全,数据在握手包里)
auth: {
token: uni.getStorageSync('token')
},
// 方式 2:query 属性(放在 URL 参数里,容易被拦截或在日志里暴露)
query: {
room: 'room_101'
}
});
后端(Node.js 示例)拦截:
javascript
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (validateToken(token)) {
next(); // 校验通过
} else {
next(new Error("鉴权失败"));
}
});
JavaScript
四、 消息去重与有序性
这是高并发下的难点。
1. 消息去重(防止重复发送)
-
前端生成
nonce(唯一 ID):每次点击发送按钮,生成一个 UUID。 -
后端校验:后端维护一个最近消息 ID 集合,如果发现 ID 重复,直接丢弃,不进行广播。
2. 有序性(防止消息乱序)
由于网络抖动,消息 A 先发可能后到。
- 解决 :每一条消息都带上一个服务端生成 的递增
sequence_id或高精度时间戳。前端拿到消息后,先按这个 ID 放入缓冲区排序,然后再渲染到屏幕上。
直接看代码吧:
// useChat.ts
import { ref } from 'vue';
import { io } from 'socket.io-client';
export const useChat = (roomId) => {
const socket = io('wss://api.thinglinks.com', {
auth: { token: 'YOUR_TOKEN' }
});
const chatList = ref([]);
// 1. 处理接收(去重与排序)
socket.on('message', (newMsg) => {
// 检查 ID 是否已存在(去重)
if (chatList.value.find(m => m.id === newMsg.id)) return;
chatList.value.push(newMsg);
// 按照时间戳排序(有序)
chatList.value.sort((a, b) => a.time - b.time);
});
// 2. 发送逻辑(区分类型)
const send = (type, content) => {
const msg = {
id: generateUUID(), // 前端先给个临时 ID
type: type, // text, image, file
content: content,
time: Date.now()
};
socket.emit('message', msg);
};
return { chatList, send };
};