JS `Server-Sent Events (SSE)`:单向实时数据流与断线重连机制

各位观众老爷们,大家好!今天咱们来聊聊一个听起来有点高大上,但其实挺接地气的技术——Server-Sent Events (SSE)。这玩意儿就像咱们看直播,主播单方面给你推送消息,你只能看,不能回复,简单粗暴,但关键时刻贼好使!

开场白:谁是SSE?

咱们先别着急上代码,先简单认识一下SSE。如果你熟悉WebSocket,那你可以把SSE当成一个“单行道”版的WebSocket。WebSocket是双向的,你来我往,可以实时聊天。SSE呢,服务器单方面推送数据给客户端,客户端只能接收,不能发消息。

SSE的应用场景:哪里用得上它?

你可能会问,既然只能单向推送,那有啥用啊?别急,用处可大了!

  • 实时更新的仪表盘: 想象一下你的股票软件,价格一直在跳动,这就是SSE的拿手好戏。
  • 服务器监控: 服务器的状态、CPU占用率,实时推送到你的监控页面。
  • 新闻推送: 新闻网站实时推送最新消息,不用你手动刷新。
  • 在线游戏: 少量数据的实时更新,比如排行榜、游戏状态等。
  • 通知系统: 比如github通知,或者网站的站内消息

总之,任何需要服务器单方面实时推送数据的场景,SSE都能派上用场。

SSE的优势:为啥不用WebSocket?

既然WebSocket是双向的,那为啥还要SSE呢?难道是程序员吃饱了撑的?当然不是!SSE有它的独到之处:

  • 简单: 协议简单,实现起来比WebSocket容易得多。
  • HTTP协议: 基于HTTP协议,天然支持各种HTTP特性,比如代理、缓存、身份验证等等。
  • 单向数据流: 对于只需要服务器推送数据的场景,单向数据流更加高效。避免了双向通信带来的额外开销。
  • 自动重连: SSE客户端会自动尝试重连服务器,无需手动编写重连逻辑。

简单来说,如果你的应用只需要服务器推送数据,SSE就是个轻量级的选择,没必要为了一个“单行道”搭一座“高速公路”。

SSE的劣势:也不是万能的

当然,SSE也有它的局限性:

  • 单向通信: 只能服务器推送数据,客户端无法主动发送消息。
  • 浏览器兼容性: 虽然主流浏览器都支持SSE,但还是有一些老旧浏览器不支持。
  • 二进制数据: 对二进制数据的支持不如WebSocket。

所以,选择SSE还是WebSocket,要根据你的实际需求来决定。

实战演练:代码说话!

光说不练假把式,咱们来点真格的。下面分别用Node.js和浏览器JS来实现一个简单的SSE应用。

1. Node.js服务器端 (SSE Server)

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/events') {
    // 设置响应头,告诉浏览器这是一个SSE连接
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    // 发送初始数据(可选)
    res.write('data: Server connected!nn');

    // 每隔1秒发送一次数据
    let counter = 0;
    const intervalId = setInterval(() => {
      const data = `data: Message ${counter}nn`;
      res.write(data);
      counter++;

      //如果counter大于10,就关闭连接
      if (counter > 10){
          clearInterval(intervalId);
          res.end();
      }
    }, 1000);

    // 监听客户端断开连接事件
    req.on('close', () => {
      clearInterval(intervalId);
      console.log('Client disconnected');
    });
  } else {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>SSE Example</h1>');
  }
});

const port = 3000;
server.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

这段代码做了什么:

  • 创建了一个HTTP服务器。
  • 监听/events路径,如果客户端请求这个路径,就建立SSE连接。
  • 设置响应头Content-Type: text/event-stream,告诉浏览器这是一个SSE连接。
  • 设置Cache-Control: no-cache,禁止缓存,保证实时性。
  • 设置Connection: keep-alive,保持连接不断开。
  • 使用setInterval每隔1秒发送一条数据。
  • 监听客户端断开连接事件,清理定时器。

2. 浏览器客户端 (SSE Client)

<!DOCTYPE html>
<html>
<head>
  <title>SSE Example</title>
</head>
<body>
  <h1>SSE Example</h1>
  <div id="output"></div>

  <script>
    const output = document.getElementById('output');
    const eventSource = new EventSource('/events'); // 连接到服务器的/events端点

    eventSource.onopen = () => {
      output.innerHTML += '<div>Connection opened!</div>';
    };

    eventSource.onmessage = (event) => {
      output.innerHTML += `<div>${event.data}</div>`;
    };

    eventSource.onerror = (error) => {
      console.error('SSE error:', error);
      output.innerHTML += '<div>Error occurred!</div>';
    };

    //关闭连接
    //setTimeout(() => {
    //  eventSource.close();
    //  output.innerHTML += '<div>Connection closed manually!</div>';
    //}, 10000);
  </script>
</body>
</html>

这段代码做了什么:

  • 创建了一个EventSource对象,连接到服务器的/events端点。
  • 监听open事件,当连接建立成功时,显示一条消息。
  • 监听message事件,当收到服务器推送的数据时,显示数据。
  • 监听error事件,当发生错误时,显示错误信息。
  • (可选)使用eventSource.close()手动关闭连接。

运行代码:见证奇迹的时刻!

  1. 把上面的Node.js代码保存为server.js,然后在命令行中运行node server.js
  2. 把上面的HTML代码保存为index.html,然后在浏览器中打开index.html

你应该能看到页面上不断显示服务器推送的数据。

断线重连:天生自带的技能

SSE客户端会自动尝试重连服务器,无需手动编写重连逻辑。你可以尝试断开服务器,然后再重新启动,看看客户端是否会自动重连。

自定义事件:让数据更有意义

SSE不仅可以发送简单的数据,还可以发送自定义事件。

1. Node.js服务器端 (自定义事件)

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/events') {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    let counter = 0;
    const intervalId = setInterval(() => {
      if (counter % 2 === 0) {
        res.write(`event: messagendata: This is a normal message ${counter}nn`);
      } else {
        res.write(`event: warningndata: This is a warning message ${counter}nn`);
      }
      counter++;

      if (counter > 10){
          clearInterval(intervalId);
          res.end();
      }
    }, 1000);

    req.on('close', () => {
      clearInterval(intervalId);
      console.log('Client disconnected');
    });
  } else {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>SSE Example</h1>');
  }
});

const port = 3000;
server.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

2. 浏览器客户端 (自定义事件)

<!DOCTYPE html>
<html>
<head>
  <title>SSE Example</title>
</head>
<body>
  <h1>SSE Example</h1>
  <div id="output"></div>

  <script>
    const output = document.getElementById('output');
    const eventSource = new EventSource('/events');

    eventSource.addEventListener('message', (event) => {
      output.innerHTML += `<div>Message: ${event.data}</div>`;
    });

    eventSource.addEventListener('warning', (event) => {
      output.innerHTML += `<div style="color: red;">Warning: ${event.data}</div>`;
    });

    eventSource.onerror = (error) => {
      console.error('SSE error:', error);
      output.innerHTML += '<div>Error occurred!</div>';
    };
  </script>
</body>
</html>

这段代码做了什么:

  • 服务器端发送event: messageevent: warning两种类型的事件。
  • 客户端使用addEventListener监听messagewarning事件,分别处理不同类型的事件。

数据格式:不只是字符串

SSE默认发送的是字符串数据,但你也可以发送JSON数据。只需要在服务器端将数据序列化成JSON字符串,然后在客户端解析JSON字符串即可。

1. Node.js服务器端 (JSON数据)

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/events') {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    let counter = 0;
    const intervalId = setInterval(() => {
      const data = {
        id: counter,
        message: `Message ${counter}`,
        timestamp: new Date().getTime()
      };
      res.write(`data: ${JSON.stringify(data)}nn`);
      counter++;

      if (counter > 10){
          clearInterval(intervalId);
          res.end();
      }
    }, 1000);

    req.on('close', () => {
      clearInterval(intervalId);
      console.log('Client disconnected');
    });
  } else {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>SSE Example</h1>');
  }
});

const port = 3000;
server.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

2. 浏览器客户端 (JSON数据)

<!DOCTYPE html>
<html>
<head>
  <title>SSE Example</title>
</head>
<body>
  <h1>SSE Example</h1>
  <div id="output"></div>

  <script>
    const output = document.getElementById('output');
    const eventSource = new EventSource('/events');

    eventSource.onmessage = (event) => {
      const data = JSON.parse(event.data);
      output.innerHTML += `<div>ID: ${data.id}, Message: ${data.message}, Timestamp: ${data.timestamp}</div>`;
    };

    eventSource.onerror = (error) => {
      console.error('SSE error:', error);
      output.innerHTML += '<div>Error occurred!</div>';
    };
  </script>
</body>
</html>

消息格式:SSE协议规范

SSE的消息格式非常简单,每条消息由一个或多个以换行符分隔的行组成。每一行都以字段名称开头,后面跟着一个冒号和一个值。常见的字段名称有:

  • data: 消息数据。
  • event: 事件名称。
  • id: 消息ID。
  • retry: 重连延迟时间(毫秒)。

例如:

data: This is a message
event: my_event
id: 12345
retry: 10000

data: Another message

服务器配置:Nginx的反向代理

如果你使用Nginx作为反向代理,需要配置Nginx支持SSE。需要在Nginx配置文件中添加以下配置:

location /events {
  proxy_pass http://your_backend_server;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_buffering off; # 关闭proxy_buffering
  gzip off; # 关闭gzip压缩,避免数据被缓存
  tcp_nodelay on; # 禁用Nagle算法,减少延迟
}

最佳实践:一些小建议

  • 保持连接: 尽量保持SSE连接不断开,避免频繁重连。
  • 心跳机制: 可以使用心跳机制来检测连接是否正常,如果连接断开,可以尝试重连。
  • 错误处理: 客户端要做好错误处理,当发生错误时,要及时提示用户。
  • 流量控制: 服务器端要做好流量控制,避免过多的并发连接导致服务器崩溃。
  • 数据压缩: 可以使用gzip压缩来减少数据传输量。

总结:SSE的魅力

Server-Sent Events (SSE) 是一种简单、高效的单向实时数据流技术。它基于HTTP协议,天然支持各种HTTP特性,并且客户端会自动重连服务器。虽然SSE只能服务器推送数据,但对于只需要单向通信的应用场景,SSE是个轻量级的选择。

表格总结:SSE vs WebSocket

特性 SSE WebSocket
通信方式 单向 (服务器 -> 客户端) 双向 (服务器 <-> 客户端)
协议 HTTP 自定义协议
复杂性 简单 复杂
重连机制 自动重连 需要手动实现重连
浏览器兼容性 良好 (主流浏览器) 良好 (主流浏览器)
应用场景 实时更新的仪表盘、服务器监控、新闻推送 实时聊天、在线游戏、协同编辑

结语:希望对大家有所帮助!

好了,今天的讲座就到这里。希望大家对Server-Sent Events (SSE) 有了更深入的了解。记住,技术是死的,人是活的,选择哪种技术要根据你的实际需求来决定。祝大家编码愉快!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注