服务器端实时推送技术之SSE
前言
在讲Server-Sent Events (SSE) 之前,我们先来看看 HTTP 请求- 响应。一个标准的 HTTP 请求- 响应,需要客户端打开一个连接,将一个 HTTP 请求(如 HTTP GET 请求)发送到服务端,然后接收到 HTTP 回来的响应,如果该响应被完全发送或者接收,服务端就会把连接关闭。通常是由某个客户发起,客户端才会需要请求所有数据。
然而, Server-Sent Events (SSE) 与 HTTP 请求- 响应背道而驰,它是一种机制,客户端一旦建立起客户机-服务器的连接,就能让服务端将数据以异步的方式从服务器推到客户端。当连接由客户端建立完成,服务端就提供数据,并决定新数据“块"可用时将其发送到客户端。当一个新的数据事件发生在服务端时,这个事件被服务端发送到客户端。因此,名称被称为 Server-Sent Events(服务器推送事件)。下面是支持服务端到客户端交互的技术总览:
-
插件提供 socket 方式:比如利用 Flash XMLSocket,Java Applet 套接口,Activex 包装的 socket。
- 优点:原生 socket 的支持,与 PC 端的实现方式相似;
- 缺点:浏览器端需要装相应的插件;与 js 进行交互时复杂
-
Polling:轮询,重复发送新的请求到服务端。如果服务端没有新的数据,就发送适当的指示并关闭连接。然后客户端等待一段时间后,发送另一个请求(例如,一秒后)
- 优点:实现简单,无需做过多的更改
- 缺点:轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担。
- Long-polling:长轮询,客户端发送一个请求到服务端,如果服务端没有新的数据,就保持住这个连接直到有数据。一旦服务端有了数据(消息)给客户端,它就使用这个连接发送数据给客户端。接着连接关闭。
- 优点:比 Polling 做了优化,有较好的时效性
- 缺点:需第三方库支持,实现较为复杂;每次连接只能发送一个数据,多个数据发送时耗费服务器性能
- 基于 iframe 及 htmlfile 的流(streaming)方式:iframe 流方式是在页面中插入一个隐藏的 iframe,利用其src属性在服务器和客户端之间创建一条长链接,服务器向 iframe 传输数据(通常是 HTML,内有负责插入信息的 javascript),来实时更新页面。
- 优点:消息能够实时到达;
- 缺点:服务器维持着长连接期会消耗资源;iframe 不规范的用法;数据推送过程会有加载进度条显示,界面体验不好
- Server-Sent events:SSE 与 长轮询机制类似,区别是每个连接不只发送一个消息。客户端发送一个请求,服务端就保持这个连接直到有一个新的消息已经准备好了,那么它将消息发送回客户端,同时仍然保持这个连接是打开,这样这个连接就可以用于另一个可用消息的发送。一旦准备好了一个新消息,通过同一初始连接发送回客户端。客户端单独处理来自服务端传回的消息后不关闭连接。所以,SSE 通常重用一个连接处理多个消息(称为事件)。SSE 还定义了一个专门的媒体类型 text/event-stream,描述一个从服务端发送到客户端的简单格式。SSE 还提供在大多数现代浏览器里的标准 javascript 客户端 API 实现。关于 SSE 的更多信息,请参见 SSE API 规范。
- 优点:HTML5 标准;实现较为简单;一个连接可以发送多个数据
- 缺点:IE 不支持 EventSource(可以使用第三方的 js 库来解决,具体可以本章中的源码) ;服务器只能单向推送数据到客户端
- WebSocket: WebSocket 与上述技术都不同,因为它提供了一个真正的全双工连接。发起者是一个客户端,发送一个带特殊 HTTP 头的请求到服务端,通知服务器, HTTP 连接可能“升级”到一个全双工的 TCP/IP WebSocket 连接。如果服务端支持 WebSocket,它可能会选择升级到 WebSocket。一旦建立 WebSocket 连接,它可用于客户机和服务器之间的双向通信。客户端和服务器可以随意向对方发送数据。此时,新的 WebSocket 连接上的交互不再是基于 HTTP 协议了。 WebSocket 可以用于需要快速在两个方向上交换小块数据的在线游戏或任何其他应用程序。(示例可以参考http://www.waylau.com/netty-websocket-chat/)
- 优点:HTML5 标准;大多数浏览器支持;真正全双工;性能强
- 缺点:实现相对复杂;ws 协议
SSE vs. WebSocket
用比较笼统的一个说法,就是WebSocket能做的,SSE也能做,反之亦然,但是它们还是有差别的,特别是在完成某些任务方面。WebSocket 是一种更为复杂的服务端实现技术,但它是真正的双向传输技术,既能从服务端向客户端推送数据,也能从客户端向服务端推送数据。WebSocket 和 SSE 的浏览器支持率差不多,除了IE。IE是个例外,即便IE11都还不支持原生 SSE,IE10 添加了WebSocket 支持,可见上图。与 WebSocket 相比,SSE 有一些显著的优势。我认为它最大的优势就是便利:不需要添加任何新组件,用任何你习惯的后端语言和框架就能继续使用。你不用为新建虚拟机、弄一个新的IP或新的端口号而劳神,就像在现有网站中新增一个页面那样简单。我喜欢把这称为既存基础设施优势。
SSE 的第二个优势是服务端的简洁。我们将在下节中看到,服务端代码只需几行。相对而言,WebSocket 则很复杂,不借助辅助类库基本搞不定。因为 SSE 能在现有的 HTTP/HTTPS 协议上运作,所以它能直接运行于现有的代理服务器和认证技术。而对 WebSocket 而言,代理服务器需要做一些开发(或其他工作)才能支持,在写这本书时,很多服务器还没有(虽然这种状况会改善)。SSE还有一个优势:它是一种文本协议,脚本调试非常容易。事实上,在本书中,我们会在开发和测试时用 curl,甚至直接在命令行中运行后端脚本。不过,这就引出了 WebSocket 相较 SSE 的一个潜在优势:WebSocket 是二进制协议,而 SSE 是文本协议(通常使用UTF-8编码)。当然,我们可以通过SSE连接传输二进制数据:在 SSE 中,只有两个具有特殊意义的字符,它们是 CR 和LF,而对它们进行转码并不难。但用 SSE 传输二进制数据时数据会变大,如果需要从服务端到客户端传输大量的二进制数据,最好还是用 WebSocket。
WebSocket 相较 SSE 最大的优势在于它是双向交流的,这意味向服务端发送数据就像从服务端接收数据一样简单。用 SSE时,一般通过一个独立的 Ajax 请求从客户端向服务端传送数据。相对于 WebSocket,这样使用 Ajax 会增加开销,但也就多一点点而已。如此一来,问题就变成了“什么时候需要关心这个差异?”如果需要以1次/秒或者更快的频率向服务端传输数据,那应该用 WebSocket。0.2次/秒到1次/秒的频率是一个灰色地带,用 WebSocket 和用 SSE 差别不大;但如果你期望重负载,那就有必要确定基准点。频率低于0.2次/秒左右时,两者差别不大。
从服务端向客户端传输数据的性能如何?如果是文本数据而非二进制数据(如前文所提到的),SSE和WebSocket没什么区别。它们都用TCP/IP套接字,都是轻量级协议。延迟、带宽、服务器负载等都没有区别。在旧版本浏览器上的兼容,WebSocket 难兼容,SSE 易兼容。
服务器端实时推送技术之SSE用法
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 服务器端实时推送技术之 SseEmitter 的用法测试 * <p> * 测试步骤: * 1.请求http://localhost:8888/sse/start?clientId=111接口,浏览器会阻塞,等待服务器返回结果; * 2.请求http://localhost:8888/sse/send?clientId=111接口,可以请求多次,并观察第1步的浏览器返回结果; * 3.请求http://localhost:8888/sse/end?clientId=111接口结束某个请求,第1步的浏览器将结束阻塞; * 其中clientId代表请求的唯一标志; * * @author syj */ @RestController @RequestMapping("/sse") public class SseEmitterController { private static final Logger logger = LoggerFactory.getLogger(SseEmitterController.class); // 用于保存每个请求对应的 SseEmitter private Map<String, Result> sseEmitterMap = new ConcurrentHashMap<>(); /** * 返回SseEmitter对象 * * @param clientId * @return */ @RequestMapping("/start") public SseEmitter testSseEmitter(String clientId) { // 默认30秒超时,设置为0L则永不超时 SseEmitter sseEmitter = new SseEmitter(0L); sseEmitterMap.put(clientId, new Result(clientId, System.currentTimeMillis(), sseEmitter)); return sseEmitter; } /** * 向SseEmitter对象发送数据 * * @param clientId * @return */ @RequestMapping("/send") public String setSseEmitter(String clientId) { try { Result result = sseEmitterMap.get(clientId); if (result != null && result.sseEmitter != null) { long timestamp = System.currentTimeMillis(); result.sseEmitter.send(timestamp); } } catch (IOException e) { logger.error("IOException!", e); return "error"; } return "Succeed!"; } /** * 将SseEmitter对象设置成完成 * * @param clientId * @return */ @RequestMapping("/end") public String completeSseEmitter(String clientId) { Result result = sseEmitterMap.get(clientId); if (result != null) { sseEmitterMap.remove(clientId); result.sseEmitter.complete(); } return "Succeed!"; } private class Result { public String clientId; public long timestamp; public SseEmitter sseEmitter; public Result(String clientId, long timestamp, SseEmitter sseEmitter) { this.clientId = clientId; this.timestamp = timestamp; this.sseEmitter = sseEmitter; } } }
前端JS接受
<script> //记录加载次数 var time=1; if (typeof (EventSource) !== "undefined") { var source = new EventSource("/sse/start?clientId=111"); console.log(source); source.addEventListener("事件名", function(e) { document.getElementById("result").innerHTML = e.data; }, false);//使用false表示在冒泡阶段处理事件,而不是捕获阶段。 } else { document.getElementById("result").innerHTML = "抱歉,你的浏览器不支持 server-sent 事件..."; } </script>
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
向强大的SVG迈进
作者:凹凸曼 - 暖暖 SVG 即 Scalable Vector Graphics 可缩放矢量图形,使用XML格式定义图形。 一、SVG印象 SVG 的应用十分广泛,得益于 SVG 强大的各种特性。 1.1、 矢量 可利用 SVG 矢量的特点,描出深圳地铁的轮廓: 1.2、iconfont SVG 可依据一定的规则,转成 iconfont 使用: 1.3、 foreignObject 利用 SVG 的 foreignObject 标签实现截图功能,原理:foreignObject 内部嵌入 HTML 元素: <svg xmlns="http://www.w3.org/2000/svg"> <foreignObject width="120" height="60"> <p style="font-size:20px;margin:0;">凹凸实验室 欢迎您</p> </foreignObject> </svg> 截图实现流程: 首先声明一个基础的 svg 模版,这个模版需要一些基础的描述信息,最重要的,它要有 &...
- 下一篇
开源啦 | go语言系统测试覆盖率收集利器goc
工程效能领域,测试覆盖率度量总是绕不开的话题,我们也不例外。在七牛云,我们主要使用go语言构建云服务,在考虑系统测试覆盖率时,最早也是通过围绕原生go test -c -cover的能力来构建。且我们已经做了很多自动化工作,能够针对很多类型的代码库,自动插桩服务,自动生成TestMain()等方法,但随着接入项目越来越多,以及后面使用场景的不断复杂化,我们发现这套方案还是有其先天局限,会让后面越来越难受: 程序必须关闭才能收集覆盖率。如果将这套系统仅定位在收集覆盖率数据上,这个痛点倒也能忍受。但是如果想进一步做精准测试等方向,就很受局限。 因为不想污染被测代码库,我们采取了自动化的方式,在编译阶段给每个服务生成类似main_test.go文件。但这种方式,其最难受的地方在于flag的处理,要知道go test命令本身会调用flag.Parse方法,所以这里需要自动化的修改源码,保证被测程序的flag定义,要先于go test调用flag.Parse之前。但是,随着程序自己使用flag姿势的复杂化,我们发现越来越难有通用方案来处理这些flag,有点难受。 受限于 go test-c命令的...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL8.0.19开启GTID主从同步CentOS8
- Mario游戏-低调大师作品
- CentOS关闭SELinux安全模块
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Red5直播服务器,属于Java语言的直播服务器
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池