IM(即时通讯)系统
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. 安装依赖
Bashnpm install socket.io-client2. 封装
TypeScriptuseSocket.tsimport { 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 示例)拦截:
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 }; };