SSE:使用 HTTP 做数据推送应用

  SSE ( Server-sent Events )是 WebSocket 的一种轻量代替方案,使用 HTTP 协议。

  严格地说,HTTP 协议是没有办法做服务器推送的,但是当服务器向客户端声明接下来要发送流信息时,客户端就会保持连接打开,SSE 使用的就是这种原理。

SSE 能做什么?

  理论上, SSE 和 WebSocket 做的是同一件事情。当你需要用新数据局部更新网络应用时,SSE 可以做到不需要用户执行任何操作,便可以完成。

  举例我们要做一个统计系统的管理后台,我们想知道统计数据的实时情况。类似这种更新频繁、 低延迟的场景,SSE 可以完全满足。

  其他一些应用场景:例如邮箱服务的新邮件提醒,微博的新消息推送、管理后台的一些操作实时同步等,SSE 都是不错的选择。

SSE vs. WebSocket

  SSE 是单向通道,只能服务器向客户端发送消息,如果客户端需要向服务器发送消息,则需要一个新的 HTTP 请求。 这对比 WebSocket 的双工通道来说,会有更大的开销。这么一来的话就会存在一个「什么时候才需要关心这个差异?」的问题,如果平均每秒会向服务器发送一次消息的话,那应该选择 WebSocket。如果一分钟仅 5 - 6 次的话,其实这个差异并不大。

  在浏览器兼容方面,两者差不多。在较早之前,每当需要建立双向 Socket 时就会使用 Flash,在 移动浏览器不支持 Flash 的情况下,WebSocket 的兼容是比较难做的。

  SSE 我认为最大的优势是便利:

  • 实现一个完整的服务仅需要少量的代码;
  • 可以在现有的服务中使用,不需要启动一个新的服务;
  • 可以用任何一种服务端语言中使用;
  • 基于 HTTP / HTTPS 协议,可以直接运行于现有的代理服务器和认证技术。

  有了这些优势,在选择使用 SSE 时就已经为自己的项目节约了不少成本。

简单示例

  下面是一个简单的示例,实现一个 SSE 服务。

服务器

‘use strict‘;

const http = require(‘http‘);

http.createServer((req, res) => {

  // 服务器声明接下来发送的是事件流
  res.writeHead(200, {
    ‘Content-Type‘: ‘text/event-stream‘,
    ‘Cache-Control‘: ‘no-cache‘,
    ‘Connection‘: ‘keep-alive‘,
    ‘Access-Control-Allow-Origin‘: ‘*‘,
  });

  // 发送消息
  setInterval(() => {
    res.write(‘event: slide\n‘); // 事件类型
    res.write(`id: ${+new Date()}\n`); // 消息 ID
    res.write(‘data: 7\n‘); // 消息数据
    res.write(‘retry: 10000\n‘); // 重连时间
    res.write(‘\n\n‘); // 消息结束
  }, 3000);

  // 发送注释保持长连接
  setInterval(() => {
    res.write(‘: \n\n‘);
  }, 12000);
}).listen(2000);

  服务器首先向客户端声明接下来发送的是事件流( text/event-stream )类型的数据,然后就可以向客户端多次发送消息。

  事件流是一个简单的文本流,仅支持 UTF-8 格式的编码。每条消息以一个空行作为分隔符。

  在规范中为消息定义了 4 个字段:

  event 消息的事件类型。客户端收到消息时,会在当前的 EventSource 对象上触发一个事件,这个事件的名称就是这个字段的值,如果消息没有这个字段,客户端的 EventSource 对象就会触发默认的 message 事件。

  id 这条消息的 ID。客户端接收到消息后,会把这个 ID 作为内部属性 Last-Event-ID,在断开重连 成功后,会把 Last-Event-ID 发送给服务器。

  data 消息的数据字段。 客户端会把这个字段解析为字符串,如果一条消息有多个 data 字段,客户端会自动用换行符 连接成一个字符串。

  retry 指定客户端重连的时间。只接受整数,单位是毫秒。如果这个值不是整数则会被自动忽略。

  一个很有意思的地方是,规范中规定以冒号开头的消息都会被当作注释,一条普通的注释(:\n\n)对于服务器来说只占 5 个字符,但是发送到客户端上的时候不会触发任何事件,这对客户端来说是非常友好的。所以注释一般被用于维持服务器和客户端的长连接

  效果:

客户端

  我们创建了一个 EventSource 对象,传入参数:url。并且根据服务器的状态和发送的信息作出响应。

‘use strict‘;

if (window.EventSource) {
  // 创建 EventSource 对象连接服务器
  const source = new EventSource(‘http://localhost:2000‘);

  // 连接成功后会触发 open 事件
  source.addEventListener(‘open‘, () => {
    console.log(‘Connected‘);
  }, false);

  // 服务器发送信息到客户端时,如果没有 event 字段,默认会触发 message 事件
  source.addEventListener(‘message‘, e => {
    console.log(`data: ${e.data}`);
  }, false);

  // 自定义 EventHandler,在收到 event 字段为 slide 的消息时触发
  source.addEventListener(‘slide‘, e => {
    console.log(`data: ${e.data}`); // => data: 7
  }, false);

  // 连接异常时会触发 error 事件并自动重连
  source.addEventListener(‘error‘, e => {
    if (e.target.readyState === EventSource.CLOSED) {
      console.log(‘Disconnected‘);
    } else if (e.target.readyState === EventSource.CONNECTING) {
      console.log(‘Connecting...‘);
    }
  }, false);
} else {
  console.error(‘Your browser doesn\‘t support SSE‘);
}

  EventSource从父接口 EventTarget 中继承了属性和方法,其内置了 3EventHandler 属性、2 个只读属性和 1 个方法:

EventHandler 属性

  EventSource.onopen 在连接打开时被调用。

  EventSource.onmessage 在收到一个没有 event 属性的消息时被调用。

  EventSource.onerror 在连接异常时被调用。

只读属性

  EventSource.readyState 一个 unsigned short 值,代表连接状态。可能值是 CONNECTING (0), OPEN (1), 或者 CLOSED (2)。

  EventSource.url 连接的 URL。

方法

  EventSource.close() 关闭连接

  效果:

SSE 如何保证数据完整性

  客户端在每次接收到消息时,会把消息的 id 字段作为内部属性 Last-Event-ID 储存起来。

  SSE 默认支持断线重连机制,在连接断开时会 触发 EventSource 的 error 事件,同时自动重连。再次连接成功时 EventSource 会把 Last-Event-ID 属性作为请求头发送给服务器,这样服务器就可以根据这个 Last-Event-ID 作出相应的处理。

  这里需要注意的是,id 字段不是必须的,服务器有可能不会在消息中带上 id 字段,这样子客户端就不会存在 Last-Event-ID 这个属性。所以为了保证数据可靠,我们需要在每条消息上带上 id 字段。

减少开销

  在 SSE 的草案中提到,"text/event-stream" 的 MIME 类型传输应当在静置 15 秒后自动断开。在实际的项目中也会有这个机制,但是断开的时间没有被列入标准中。

  为了减少服务器的开销,我们也可以有目的的断开和重连。

  简单的办法是服务器发送一个 关闭消息并指定一个重连的时间戳,客户端在触发关闭事件时关闭当前连接并创建 一个计时器,在重连时把计时器销毁 。

‘use strict‘;

function connectSSE() {
  if (window.EventSource) {
    const source = new EventSource(‘http://localhost:2000‘);
    let reconnectTimeout;

    source.addEventListener(‘open‘, () => {
      console.log(‘Connected‘);
      clearTimeout(reconnectTimeout);
    }, false);

    source.addEventListener(‘pause‘, e => {
      source.close();
      const reconnectTime = +e.data;
      const currentTime = +new Date();
      reconnectTimeout = setTimeout(() => {
        connectSSE();
      }, reconnectTime - currentTime);
    }, false);
  } else {
    console.error(‘Your browser doesn\‘t support SSE‘);
  }
}

connectSSE();

浏览器兼容

  向下兼容:早些时候,为了实现数据实时更新最常见的方法就是轮询。

  轮询是以一个固定频率向服务器发送请求,服务器在有 数据更新时 返回新的数据,以此来管理数据的更新。这种轮询的方式不但开销大,而且更新的效率和频率有关,也不能达到及时更新的目的。

  接着便出现了长轮询的方式:客户端向服务器发送请求之后,服务器会暂时把请求挂起,等到有数据更新时再返回最新的数据给客户端,客户端在接收到新的消息后再向服务器发送请求。与常规轮询的不同之处是:数据可以做到实时更新,可以减少不必要的开销。

  这里有一个「选择长轮询还是常规轮询?」的命题,长轮询是不是总比常规轮询占有优势?我们可以从带宽占用的角度分析,如果一个程序数据更新太过频繁,假设每秒 2 次更新,如果使用长轮询的话每分钟要发送 120 次 HTTP 请求。如果使用常规轮询,每 5 秒发送一次请求的话, 一分钟才 20 次,从这里看,常规轮询更占有优势。

  长轮询和 SSE 最关键的区别在于,每一次数据更新都需要一次 HTTP 请求。和 WebSocket 还有 SSE 一样,长轮询也会 占用一个 socket。在数据更新效率上和 SSE 差不多,一有数据更新就能检测到。加上所有浏览器都支持,是一个不错的 SSE 替代方案。

  文章介绍了 SSE 的用法及使用过程中的一些技巧。对比 WebSocket,SSE 在开发时间和成本上占有较大的优势。做数据推送服务,除了 WebSocket,SSE 也是一个不错的选择,希望对大家有所帮助。

原文地址:https://www.cnblogs.com/goloving/p/9196066.html

时间: 2024-11-05 02:18:24

SSE:使用 HTTP 做数据推送应用的相关文章

HTML5数据推送SSE原理及应用开发

JavaScript表达行为,CSS表达外观,注意HTML既表达结构(逻辑结构),又表达内容(数据本身)通常需要更新数据时,并不需要更新结构,正是这种不改变组织结构仅改变数据的诉求,推动了数据拉取和数据推送技术的产生. SSE是一种允许服务器端向客户端推送新数据(简称数据推送)的HTML5技术.数据推送有两种替代方案:无更新方案和数据拉取方案.http://tieba.yunxunmi.com/tieba-html5.html 无更新方案: 加载完毕HTML之后,得到一个HTML页面,之后浏览器

HTML5支持服务器发送事件(Server-Sent Events)-单向消息传递数据推送(C#示例)

传统的WEB应用程序通信时的简单时序图: 现在Web App中,大都有Ajax,是这样子: HTML5有一个Server-Sent Events(SSE)功能,允许服务端推送数据到客户端.(通常叫数据推送).基于数据推送是这样的,当数据源有新数据,它马上发送到客户端,不需要等待客户端请求.这些新数据可能是最新闻,最新股票行情,来自朋友的聊天信息,天气预报等. 数据拉与推的功能是一样的,用户拿到新数据.但数据推送有一些优势. 你可能听说过Comet, Ajax推送, 反向Ajax, HTTP流,W

javascript之数据推送

我们使用ajax与后台服务进行交互,常常是通过触发事件来单次交互,但对于有些web应用来说,需要前台与后台保持长连接,前端不定时地接收后台推送的数据信息, 例如:股票行情分析.聊天室和网页在线游戏等. 怎么做到呢? 最笨的方法: 前端需要隔一段时间向服务器发送一次ajax,请求成本太昂贵呀,就好比,你每次约炮,都得开房,还是租一个便宜点儿房子合算. 所以不能这么做. 1.comet 原理:comet是基于HTTP长连接的服务器推送技术,是一种web应用架构,服务器会主动以异步的方式向客户端程序推

HTML5数据推送应用开发

javascript表达行为,css表达外观,注意HTML既表达结构(逻辑结构),又表达内容(数据本身)通常需要更新数据时,并不需要更新结构,正是这种不改变组织结构仅改变数据的诉求,推动了数据拉取和数据推送技术的产生. SSE是一种允许服务器端向客户端推送新数据(简称数据推送)的HTML5技术.数据推送有两种替代方案:无更新方案和数据拉取方案. 无更新方案: 加载完毕HTML之后,得到一个HTML页面,之后浏览器会请求图片.CSS文件和JavaScript文件等,他们都是浏览器可以缓存的静态文件

使用Node.js实现数据推送

业务场景:后端更新数据推送到客户端(Java部分使用Tomcat服务器). 后端推送数据的解决方案有很多,比如轮询.Comet.WebSocket. 1. 轮询对于后端来说开发成本最低,就是按照传统的方式处理Ajax请求并返回数据,在学校的时候实验室的项目一直都采用轮询,因为它最保险也最容易实现.但轮询带来的通信资源的浪费是无法忽视的,无论数据是否改变,都照常发送请求并响应,而且每次HTTP请求都带有很长的头部信息. 2. Comet的概念是长连接,客户端发送请求后,后端将连接保持下来,直到连接

[HTML5_Web Workers+Sockets]HTML5 通信API 跨域门槛将不再高、数据推送也不再是梦

前言 HTML5新增通信相关两个API,跨文档消息传输与WEB Sockets API, 跨文档消息传输功能,可以在不同网页文档,不同端口(跨域情况下)进行消息传递. 使用web sockets api 可以让客户端与服务器端通过socket端口传递数据,这样便可以使用数据推送技术. 跨文档消息传输 在之前我们若想跨域获取信息会花很多功夫,现在只要获取网页所在窗口对象实例变可以实现互相通信. 首先要想从其他窗口接受发过来的消息需要对其窗口对象进行监听: window.addevntListene

一种家庭数据入口推广策略,全新的数据推送方式

目前个人消费大块数据(如视频.大型软件游戏等)的场合主要是在家庭,加上智能家居等需求,家庭数据入口有比较大的应用前景.现在推广数据入口的方式主要是智能路由器产品.但是推广速度不是很理想.一是免费送不起:二是个性化消费品涉及硬盘失效.产品更新及个人爱好等,不适合房产开发商统一部署:三是推送数据的能力比较弱.这里讨论另一种推送方式. 假设你是推广者,我是消费者,开始讲故事. 首先你跟一家小区宽带或者二级运营商(后面再讲大运营商)合作了.他们在一个出口上会有大量用户进入公网.从这里上公网的流量对他们是

C# 数据推送 实时数据推送 轻量级消息订阅发布 多级消息推送 分布式推送

前言 本文将使用一个NuGet公开的组件技术来实现数据订阅推送功能,由服务器进行推送数据,客户端订阅指定的数据后,即可以接收服务器推送过来的数据,包含了自动重连功能,使用非常方便 nuget地址:https://www.nuget.org/packages/HslCommunication/            github地址:https://github.com/dathlin/HslCommunication                                 如果喜欢可以s

Zzzj Submit:WordPress 百度熊掌号/原创保护文章数据推送插件 V3.4

Zzzj Submit:WordPress 百度熊掌号/原创保护文章数据推送插件 V3.4 Zzzj Submit是一款根据百度站长平台原创保护提交接口而升级开发的WordPress插件,目前插件已经开始适配百度熊掌号(原百度官方号)的数据提交内测,可实现用户自动自主的将文章推送至官方号及原创保护文章推送,支持定时文章推送. 即日起"Zzzj Submit"插件将从WordPress百度原创保护提交插件升级为WordPress百度熊掌号数据提交插件,由于百度熊掌号目前属于内测期间,故该