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格式轻量、易读,适合传输结构化数据;二进制数据传输效率高,但可读性差。要根据实际应用需求选择合适的数据格式,避免因数据格式问题导致连接异常

javascript 复制代码
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');
javascript 复制代码
代码说明
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框架作为演示:

javascript 复制代码
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:

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,它会自动降级为使用轮询方式进行通信。

客户端代码

javascript 复制代码
// 客户端代码
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方案:

javascript 复制代码
//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));
    },
  },
};
相关推荐
C++ 老炮儿的技术栈2 小时前
Tcp客户端报错原因分析
linux·c语言·网络·c++·网络协议·tcp/ip
Shanxun Liao2 小时前
WIN2022 搭建 HTTP 文件索引服务的完整步骤
网络·网络协议·http
C++chaofan2 小时前
RPC 框架序列化器实现深度解析
java·开发语言·网络·网络协议·rpc·序列化器
傻啦嘿哟13 小时前
2026代理IP服务商深度测评:8家主流厂商的“极限压力测试“全记录
网络协议·tcp/ip·压力测试
%小农14 小时前
在cursor中使用server
网络·网络协议·http
ivy1598683771515 小时前
芯锦科技 HP9117 多协议USB Type-A快充识别芯片
网络·科技·网络协议·5g·信号处理·p2p
西红市杰出青年16 小时前
MCP 的三种数据传输模式教程(stdio / SSE / Streamable HTTP)
网络·网络协议·http·ai
.select.16 小时前
HTTPS 如何优化?
网络协议·http·https
三三有猫18 小时前
HTTP、HTTPS和SOCKS代理怎么选?
网络协议·http·https