SSE WebSocket Socket.IO 三者使用及区别
WebSocket
WebSocket是一种在单个TCP连接上进行全双工通信的协议,它允许在客户端和服务器之间进行实时的双向数据传输。WebSocket通过一个持久的连接,使得服务器能够主动向客户端推送数据,而不需要客户端发起请求。
一、建立合理的重连机制
在网络环境复杂多变的情况下,WebSocket连接可能会出现断开的情况。这时,一个合理的重连机制就显得尤为重要。我们可以在代码中设置重连的间隔时间,避免频繁重连导致服务器压力过大。同时,也要注意重连的次数限制,防止陷入死循环[^1^]。例如,可以采用指数退避算法,每次重连失败后,将重连间隔时间逐渐增加,这样既能保证连接的稳定性,又能避免对服务器造成过大负担。
二、实施有效的心跳机制
心跳机制是维持WebSocket连接稳定的关键。通过定期发送心跳包,客户端和服务器可以互相确认连接状态,及时发现并处理断线情况[^3^]。心跳包的内容可以是简单的字符串,也可以是特定的JSON格式数据。需要注意的是,心跳包的发送频率要根据实际应用需求来设置,过于频繁会增加网络流量,过于稀疏则可能无法及时发现连接问题。一般来说,每隔30秒到1分钟发送一次心跳包是比较合理的。
三、优化网络环境
网络环境的稳定性直接影响WebSocket连接的质量。在开发过程中,我们要尽量选择稳定的网络环境进行测试和部署。如果应用需要在移动网络环境下使用,要考虑到网络切换、信号不稳定等问题。可以使用网络状态检测工具,实时监测网络状况,当网络不稳定时,及时采取措施,如切换到备用服务器或提示用户切换网络[^2^]。
四、合理配置服务器
服务器的性能和配置也会影响WebSocket连接的稳定性。要确保服务器有足够的资源来处理WebSocket连接,包括CPU、内存、带宽等。同时,服务器的网络配置也很重要,要确保防火墙、负载均衡等设备不会阻止WebSocket连接。可以使用性能测试工具,对服务器进行压力测试,找出性能瓶颈,进行优化[^4^]。
五、选择合适的数据格式
在WebSocket通信中,选择合适的数据格式可以提高传输效率和稳定性。常见的数据格式有JSON、XML、二进制数据等。JSON格式轻量、易读,适合传输结构化数据;二进制数据传输效率高,但可读性差。要根据实际应用需求选择合适的数据格式,避免因数据格式问题导致连接异常
class WebSocketClient { private socket: WebSocket; private reconnectInterval: number = 3000; // 重连间隔时间(毫秒) private maxReconnectAttempts: number = 5; // 最大重连次数 private reconnectAttempts: number = 0; private pingInterval: number | undefined; private heartbeatInterval: number = 30000; // 心跳间隔时间(毫秒) private heartbeatTimeout: number | undefined; constructor(private url: string) { this.initWebSocket(); } private initWebSocket() { this.socket = new WebSocket(this.url); this.setupEventHandlers(); this.setupHeartbeat(); } private setupEventHandlers() { this.socket.addEventListener('open', () => { console.log('WebSocket 连接成功'); this.reconnectAttempts = 0; }); this.socket.addEventListener('message', (event) => { console.log('收到消息:', event.data); // 处理服务器发送的消息 }); this.socket.addEventListener('error', (error) => { console.error('WebSocket 错误:', error); this.reconnect(); }); this.socket.addEventListener('close', (event) => { console.log('WebSocket 关闭,代码:', event.code, '原因:', event.reason); this.reconnect(); }); } private setupHeartbeat() { this.pingInterval = setInterval(() => { if (this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify({ type: 'ping' })); this.heartbeatTimeout = setTimeout(() => { console.log('心跳超时,尝试重连'); this.socket.close(); }, this.heartbeatInterval * 2); } }, this.heartbeatInterval); } private reconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; console.log('尝试重新连接 WebSocket,第', this.reconnectAttempts, '次'); setTimeout(() => { this.initWebSocket(); }, this.reconnectInterval * this.reconnectAttempts); } else { console.error('达到最大重连次数,停止重连'); } } public send(message: string) { if (this.socket.readyState === WebSocket.OPEN) { this.socket.send(message); } else { console.error('WebSocket 连接未开启,无法发送消息'); } } public close() { this.socket.close(); clearInterval(this.pingInterval); clearTimeout(this.heartbeatTimeout); } } // 示例用法 const wsClient = new WebSocketClient('ws://example.com/socket'); wsClient.send('Hello, Server');代码说明 1.重连机制: - 当 WebSocket 连接关闭或出现错误时,自动尝试重连。 - 重连间隔时间采用指数递增策略,避免频繁重连。 - 设置最大重连次数,防止无限重连。 2.心跳机制: - 定期向服务器发送心跳包(ping),保持连接活跃。 - 如果服务器在一定时间内没有响应心跳包,认为连接断开,触发重连。 3.错误处理: - 捕获 WebSocket 错误事件,记录错误日志。 4.数据发送: - 提供便捷的 send 方法,确保消息在 WebSocket 处于开放状态时发送。 5.连接关闭: - 提供 close 方法,支持手动关闭 WebSocket 连接,并清理定时器。 使用说明 1.初始化 WebSocket 客户端时,传入目标 WebSocket 服务器的 URL。 2.使用 send 方法发送消息至服务器。 3.使用 close 方法手动关闭连接。 总之,打造高效、稳定的WebSocket连接需要从多个方面入手,包括建立合理的重连机制、实施有效的心跳机制、优化网络环境、合理配置服务器以及选择合适的数据格式等。希望这些技巧能帮助大家解决WebSocket连接中遇到的问题,提升应用的实时通信性能。EventSource (SSE)
EventSource 也叫作"server-sent-event" 。 是HTML5引入的一种轻量级的、基于文本的协议,用于从服务器推送事件。与WebSocket不同,EventSource建立在HTTP协议之上,使用了单向的服务器推送。它允许服务器发送事件到客户端,但客户端只能接收而不能发送。
服务端实现:
在服务端,使用Node.js和Express框架作为演示:
const express = require('express'); const { v4: uuidv4 } = require('uuid'); const app = express(); const port = 3000; const clients = new Map(); app.get('/events', (req, res) => { const clientId = uuidv4(); const newClient = res; clients.set(clientId, newClient); req.on('close', () => { clients.delete(clientId); }); res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); clients.forEach((client) => { client.write(`data: A new user joined!\n\n`); }); }); app.post('/send-message', express.json(), (req, res) => { const { message } = req.body; clients.forEach((client) => { client.write(`data: ${message}\n\n`); }); res.status(200).send('Message sent successfully!'); }); app.listen(port, () => { console.log(`Server is listening at http://localhost:${port}`); });客户端实现
在浏览器端,使用JavaScript:
const eventSource = new EventSource('http://localhost:3000/events'); eventSource.onmessage = (event) => { const message = event.data; console.log(`Received message: ${message}`); }; document.getElementById('sendMessageBtn').addEventListener('click', () => { const message = prompt('Enter your message:'); fetch('http://localhost:3000/send-message', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message }), }); });Socket.io
Socket.IO说明
socket.io 是一个基于websocket的实时通信库,它提供了更简单、更高级的API,使得实时通信变得更容易上手。
socket.io 自动选择最佳的通信方式,如果浏览器不支持websocket,它会自动降级为使用轮询方式进行通信。
客户端代码
// 客户端代码 const socket = io('http://localhost:3000'); socket.on('connect', () => { console.log('Socket.io connection established!'); }); socket.on('message', (data) => { console.log('Received message data:', data); }); socket.on('disconnect', () => { console.log('Socket.io connection closed!'); }); socket.emit('message', 'Hello, server!'); // 服务端代码const io = require('socket.io')(3000); io.on('connection', (socket) => { console.log('A new client is connected!'); socket.on('message', (data) => { console.log('Received message data:', data); socket.emit('message', `You said: ${data}`); }); socket.on('disconnect', () => { console.log('Client disconnected!'); }); });三者优缺点:websocket ,eventSource,socket.io
1、EventSource
eventsource是HTML5中新增的API,它提供了一种简单易用的方式来实现服务器向浏览器的即时推送。通过eventsource,我们可以建立一个持久连接,从而实现服务器端的事件推送到浏览器端。这使得我们能够轻松创建一个实时聊天室或实时数据展示页面。
eventsource具有超高效、最好用的特点。它使用简单,只需要通过JavaScript代码创建一个eventsource对象,并指定服务器端的URL。然后,我们可以监听事件,处理服务器端发送的消息。最重要的是,eventsource基于HTTP协议,所以可以兼容大部分浏览器。
优点:
- 简单易用,与 HTTP 协议兼容。
- 只需要一个长连接,服务器可以推送任意数量的事件。
- 适用于服务端向客户端发送频率较低的数据。
- 可以自动重连,并且在连接断开时会触发 error 和 close 事件,方便处理异常情况。
缺点:
- 不支持双向通信。
- 不支持二进制数据传输。
- 兼容性存在问题,不支持 IE 浏览器。
场景:
- 实时数据展示:eventsource可以用于实时展示服务器端的数据变化。比如,一个监控系统可以通过eventsource与服务器建立连接,服务器端监测到数据变化后,即时推送给客户端,客户端可以实时展示最新的数据。
- 实时聊天应用:eventsource可以用于实现实时聊天功能。当有新消息时,服务器可以使用eventsource向客户端推送消息,客户端即时收到消息并进行展示。这样就可以实现类似微信、QQ等实时聊天的功能。
- 实时通知提醒:eventsource可以用于发送实时通知提醒。比如,一个新闻网站可以使用eventsource向用户发送最新的新闻推送,用户无需手动刷新页面即可获取到最新的新闻内容。
- 实时博客评论:eventsource可以用于实时更新博客评论。当有新的评论提交时,服务器可以使用eventsource将新评论推送给其他正在浏览该博客的用户,实现实时更新评论的效果。
2、websocket
websocket 与eventsource不同,websocket是一种全双工通信协议,它能够在浏览器和服务器之间建立双向通信的连接。相比于eventsource的单向通信,websocket可以同时实现浏览器向服务器的推送和服务器向浏览器的推送,实现真正的双向通信。
websocket具有极低的延迟,适用于实时游戏、聊天应用等场景。它使用ws或wss协议,能够在浏览器和服务器之间建立长时间的连接。通过websocket,我们可以发送和接收消息,实时更新数据,并处理各种事件。
优点:
- 支持双向通信,客户端和服务端都可以发送和接收消息;
- 可以发送二进制数据,支持大文件传输;
- 协议比较轻量级,能够节省网络带宽和服务器资源;
- 兼容性较好,大部分现代浏览器都支持 WebSocket。
缺点:
- 需要在服务端实现 WebSocket 协议的支持;
- 相对于 HTTP 请求来说,WebSocket 连接需要占用更多的服务端资源;
- 安全性问题:需要注意防止 CSRF 和 XSS 攻击,避免恶意用户利用 WebSocket 劫持会话或注入脚本等。
3、socket.io
socket.io是一个基于websocket的实时通信库,它提供了更简单、更高级的API,使得实时通信变得更容易上手。socket.io能够自动选择最佳的通信方式,如果浏览器不支持websocket,它会自动降级为使用轮询方式进行通信。
socket.io不仅提供了基本的通信功能,还提供了一些小技巧来简化开发过程。例如,它支持房间(Room)概念,允许将多个用户分组在同一个房间中。这样,我们可以通过向特定房间发送消息来实现群聊等功能。
优点:
- 支持双向通信。
- 支持广播和房间功能,使得开发者可以轻松地实现实时应用程序。
- 自带多种传输方式,如 WebSocket、HTTP 长轮询、JSONP 等,可以根据浏览器或设备的不同选择最佳传输方式。
缺点:
- 使用 Socket.IO 的应用程序需要使用 Socket.IO 作为通信层,不能在应用程序中集成原生 WebSocket 或 EventSource。
- 对比 EventSource 和 WebSocket,Socket.IO 相对来说更加庞大,需要引入相应的客户端库和服务器端插件,如果应用程序只需要简单的实时通信,使用 EventSource 或 WebSocket 可能更加适合
Uniapp 使用SSE兼容APP方案:
//Uniapp 中使用 EventSource(SSE) 实现服务器推送功能 // 安装 EventSourcePolyfill 和 axios: // // npm install event-source-polyfill // npm install axios //App 端不支持原生的 EventSource,需要使用 renderjs 来实现跨平台兼容 <script module="renderScript" lang="renderjs"> import { EventSourcePolyfill } from 'event-source-polyfill'; export default { data() { return { IsSupportSse: 'EventSource' in window, }; }, mounted() { window.addEventListener('beforeunload', () => { this.source.close(); // 页面关闭时关闭连接 }); this.sse(); // 初始化 SSE 连接 }, methods: { // 发送数据到 service 层 emitData() { // #ifdef APP UniViewJSBridge.publishHandler('onWxsInvokeCallMethod', { cid: this._$id, method: 'acceptDataFromRenderjs', args: { type: this.messageData, }, }); // #endif setTimeout(() => { this.$ownerInstance.callMethod('acceptDataFromRenderjs', { type: this.messageData, }); }, 200); }, // 初始化 SSE 连接 sse() { if ('EventSource' in window) { console.log('SSE 连接中...'); this.source = new EventSourcePolyfill( `http://140.143.171.167:8080/api/common/sse/createConnect?clientId=${plus.storage.getItem('token')}`, { headers: { token: plus.storage.getItem('token'), 'Content-Type': 'application/x-www-form-urlencoded', }, heartbeatTimeout: 10 * 60 * 1000, // 重连时间间隔 } ); this.source.onmessage = (e) => { this.messageData = e.data; // 接收服务器推送的数据 this.emitData(); // 发送数据到 service 层 }; this.source.onopen = () => { console.log('---连接打开---'); }; this.source.onerror = (e) => { if (e.readyState === EventSource.CLOSED) { console.log('---连接关闭---'); } else { console.log('onerror:', e.readyState); } }; } else { console.log('当前环境不支持 SSE'); } }, }, }; </script> //在 script 层中,通过 acceptDataFromRenderjs 方法接收 renderjs 层传递的数据,并处理显示 export default { data() { return { curStreamMsgObj: null, // 当前接收到的数据 }; }, methods: { async acceptDataFromRenderjs(options) { // 控制页面向下滚动到底部 uni.pageScrollTo({ scrollTop: 99999999999999999, duration: 0, }); if (this.curStreamMsgObj) { // 正常接收数据,追加显示到前端 this.curStreamMsgObj.content += JSON.parse(options.type); } else { // 第一次接收数据 this.curStreamMsgObj = { role: 'gpt', content: JSON.parse(options.type), }; this.gpt.push(this.curStreamMsgObj); // 添加到消息列表 await this.$nextTick(); } console.log('接收 renderjs 发回的数据:', JSON.parse(options.type)); }, }, };