1 引言
这个问题第一次是我在实现一个导师的方案的时候所发现的,一开始我需要实现服务器与客户端的密钥协商和数据传递,服务器需要主动分发(推送)密钥给客户端,因为以前没有做过相关编码,后来只能想到用反向连接,也就是交换C/S的身份。
再后来是在做一个机器学习的问题时候,因为机器学习模型的运行需要综合多个客户端(边缘节点)的数据,然后得到结果,而且各个客户端的数据传输是不一致的,时间和数据量不定。所以需要在服务器处理各个客户端的数据,得到结果后主动推送结果给客户端。所以准备研究一下这个问题:服务器主动推送消息数据给客户端,包括web服务器与浏览器,后台服务器与Android,服务器之间。
定义:推送技术是指通过客户端与服务器端建立连接,客户端可以接收由服务器端不定时主动发送的消息。
2 相关技术
这种技术分析来看在web端和Android端应该用的很多。比如服务器主动推送一条优惠信息给某个客户端,实时提醒客户端有线上订单消息,类似于邮件、消息、待办事项等的通知。
2.1 B/S架构
B/S架构的标准是基于HTTP协议,客户端发送请求,服务器给出响应的一问一答形式进行通信。这就意味着如果客户端不主动地向服务器发送消息,服务器就无法主动给客户端推送消息。
随着HTML、浏览器等各项技术、标准的发展,依次生成了不同的手段与方法能够实现服务端主动推送消息,包括AJAX,Comet,ServerSent以及WebSocket。
适用场景:实时股票价格、商品价格、实时新闻、Twitter/weibo timeline、基于浏览器的聊天系统。
F5手动刷新 --> AJAX轮询(Polling) --> Comet实时更新 --> HTML5实时通信。
meta标签
在 Web早期,通过配置meta标签让浏览器自动刷新,从而实现服务器端的推送
<META HTTP-RQUIV="Refresh" CONTENT=12>
优点:使用方式简单,可以在JS禁用情况下使用
缺点:不是实时更新数据,对服务器造成的压力大,带宽浪费多
2.1.1 AJAX轮询
正常的一个页面在浏览器中是这样工作的:
- 用户向浏览器输入需要访问的地址URL
- 浏览器根据URL访问服务器,并与服务器之间创建一个TCP连接(HTTP请求,也有可能是UDP)
- 服务器根据这个URL和一些参数,回复一个响应,可能是一段HTML文本,也可能是一个图片。将其写入TCP连接,然后关闭连接(长连接则会保持多个请求-响应)
- 浏览器得到了来自服务器的HTML文本,解析并呈现渲染到浏览器上给用户浏览
此时,用户点击了网站上任何一个<a>或触发任何一个<form>提交时:
- 浏览器根据form的参数或者a的参数,作为访问的地址
- 与服务器创建TCP连接
- 服务器组建HTML文本,然后关闭连接
- 浏览器将当前显示的页面摧毁,并按照新的HTML文本呈现一个新的页面给用户
我们不难发现的是整个过程中间,一旦建立了一个连接,页面就无法再维护住了。
Http轮询:
即定时的通过Ajax查询服务器端,客户端定时向服务器端发送ajax请求,服务器端接收到请求后马上响应信息并关闭连接。要求两次请求间隔时间必须尽可能的小,但若时间间隔减小,客户端浏览器在相同时间内就会发出更多的请求,这些请求中大部分都不会返回有用的数据,这会白白地浪费掉带宽和处理资源。
JSONP轮询:
JSONP轮询和HTTP轮询类似,不同之处在于使用JSONP可以发送跨域请求(请求不属于您所在的域),JSONP请求通常可以通过它的调用参数和返回内容识别出来,其是可执行的JavaScript代码。要想在 JavaScript 中实现轮询,可以使用setInterval来定期地发出 Ajax 请求。
这种技术实现起来非常简单,但它不具有伸缩性,需要不断地向服务器端发送消息,会对服务器造成极大的性能浪费,加重网络负载,拖累服务器。
Piggyback polling(捎带轮询):
捎带轮询是一种比轮询更聪明的做法,它会删除所有非必需的请求(没有返回数据的那些),且不存在时间间隔,客户端在需要的时候向服务器端发送请求。不同之处在于响应的部分,响应被分成两个部分:对请求数据的响应和对服务器时间的响应。捎带轮询通常针对服务器端的所有 Ajax 请求可能会返回一个混合的响应。
这种方法因为客户端控制了何时发送请求,所以没有不返回数据的请求,对资源的消耗较少,可用在所有浏览器上。但这仍然算是客户端主动去请求服务器端,当服务器累积了事件想要传送端户端时,在客户端没有发送请求时也不能主动发送给客户端。
此时我们学习一下XmlHttpRequest组件,这个组件提供我们手动创建一个HTTP请求,发送我们想要的数据,服务器也可以只返回我们想要的结果,最大的好处是,当我们收到服务器的响应时,原来的页面没有被摧毁。这就好比,我喊一句"我的咖啡喝完了,我要续杯",然后服务员就拿了一杯咖啡过来,而不是会把我没吃完的套餐全部倒掉。术语就是局部刷新,增量式的更新。
其实就是定时的通过Ajax查询服务端,客户端按规定时间定时像服务端发送ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
当我们利用AJAX实现服务器推送时,其实质是客户端不停地向服务器询问"有没有给我的消息呀?",然后服务器回答"有"或"没有"来达到的实现效果。它的实现方法也很简单,利用jQuery框架封装好的AJAX调用也很方便。
其存在一个问题:
- 间隔时间越快,推送的及时性越好,服务器的消费越大;
- 间隔时间越慢,推送的及时性越低,服务器的消费越小。
还有一个类似的轮询是使用JSONP跨域请求的方式轮询,在实现起来有差别,但基本原理都是相同的,都是客户端不断的向服务器发起请求。
优点:
服务端逻辑简单,编码实现简单。
缺点:
这是通过模拟服务器发起的通信,不是实时通信,不顾及应用的状态改变而盲目检查更新,导致服务器资源的浪费,且会加重网络负载,拖累服务器。其中大多数请求可能是无效请求,在大量用户轮询很频繁的情况下对服务器的压力很大。
应用:并发用户量少,而且要求消息的实时性不高,一般很少采用。
会造成数据同步不及时及无效的请求,增加后端处理压力。
总结:严格地来说,这种实际方式,并不是真正意义上的服务器主动推送消息,但由于早期技术手段缺乏,所以AJAX轮循成为了一种很普遍的手段。
2.1.2 Comet
轮询方式的实时性是不够的,比如基于Web的聊天功能,对实时性要求就很高。于是,comet出现了。Comet是基于HTTP长连接的服务器推送技术(long lived http),主要有流(streaming)方式和长轮询(long-polling)方式,都是基于AJAX。Comet工作原理:用户发起请求后就挂起,等待服务器返回数据,在此期间不会断开连接。流方式和长轮询方式的区别是:对于流方式,客户端发起连接就不会断开连接,而是由服务器端进行控制。当服务器端有更新时,刷新数据,客户端进行更新;而对于长轮询,当服务器端有更新返回,客户端先断开连接,进行处理,然后重新发起连接。
基于 HTTP 长连接的 "服务器推送" 技术,能使服务器端主动以异步的方式向客户端程序推送数据,而不需要客户端显式的发出请求,目前有两种实现方式:
1. 基于 AJAX 的长轮询(long-polling)方式(长连接)
Ajax 的出现使得 JavaScript 可以调用 XMLHttpRequest 对象发出 HTTP 请求,JavaScript 响应处理函数根据服务器返回的信息对 HTML 页面的显示进行更新。使用 AJAX 实现 "服务器推送" 与传统的 AJAX 应用不同之处在于:
- 服务器端会阻塞请求直到有数据传递或超时才返回。(不是在一个长连接里客户端轮询,而是基于请求-响应模型,在一个长连接里等待服务器响应,称之为长轮询)
- 客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
- 当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达。这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端所有的信息取回。
在Ajax轮询基础上做的一些改进,在没有更新的时候不再返回空响应,而且把连接保持到有更新的时候,客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
普通Ajax轮询与基于Ajax的长轮询原理对比:
基于长轮询的服务器推模型
从上图可以看出,客户端发出请求后挂起,服务端在接到请求也挂起,等待有更新时返回数据并断掉连接,然后客户端再发起新的连接。
相对于"轮询"(poll),这种长轮询方式也可以称为"拉"(pull)。因为这种方案基于 AJAX,具有以下一些优点:请求异步发出;无须安装插件;IE、Mozilla FireFox 都支持 AJAX。长轮询 (long polling) 是在打开一条连接以后保持并等待服务器推送来数据再关闭,可以采用HTTP长轮询和XHR长轮询两种方式:
(1). HTTP 和JSONP方式的长轮询
把 script 标签附加到页面上以让脚本执行。服务器会挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个 script 标签来获取下一个事件,从而实现长轮询的模型。
实现可以使用 script 标签或是单纯的XMLHttpRequest对象来实现 HTTP 长轮询。
(2).XHR长轮询
这种方式是使用比较多的长轮询模式。客户端打开一个到服务器端的 AJAX 请求然后等待响应;服务器端需要一些特定的功能来允许请求被挂起,只要一有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接;如此循环。现在浏览器已经支持CROS的跨域方式请求,因此HTTP和JSONP的长轮询方式是慢慢被淘汰的一种技术,建议采用XHR长轮询。
优点:
- 客户端很容易实现良好的错误处理系统和超时管理,实现成本与Ajax轮询的方式类似。
- 实时性高,无消息的情况下不会进行频繁的请求。
缺点:
- 需要服务器端有特殊的功能来临时挂起连接。当客户端发起的连接较多时,服务器端会长期保持多个连接,具有一定的风险。
- 服务器维持着连接期间会消耗资源。
注>>在这里简单的说明下长轮询,长连接的概念。
轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
优点:后端程序编写比较容易。
缺点:请求中有大半是无用,浪费带宽和服务器资源。
实例:适于小型应用。
长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
long poll的原理是:客户端与服务器将建立一条长连接,也就是说,客户端会发出一个请求,而服务器,将阻塞请求,直到有数据需要传递,才会返回。 返回之后,客户端将关闭此连接,然后再次发出一个请求,建立一个新的连接,再次等待服务器推送数据。客户端与服务器端的连接,会一直保存着,当发生改变时,服务器才会把数据推送给客户端。这其实,就是设计模式中的观察者模式。
优点:在无消息的情况下不会频繁的请求。缺点:服务器hold连接会消耗资源。
实例:WebQQ、Hi网页版、Facebook IM。
另外,对于长连接和socket连接也有区分:
长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。一般指利用心跳包保持一个连接,从而延长连接持续时间。(持久连接一个HTTP连接,发送多个请求和响应)。
优点:消息即时到达,不发无用请求。缺点:服务器维护一个长连接会增加开销。
实例:Gmail聊天 Flash
Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序,JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。
优点:实现真正的即时通信,而不是伪即时。缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。
实例:网络互动游戏。
2. 基于iframe 及 htmlfile 的流(streaming)方式
iframe 是很早就存在的一种 HTML 标记, 通过在 HTML 页面里嵌入一个隐蔵frame,然后将这个隐蔵frame的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。iframe 流方式是在页面中插入一个隐藏的 iframe,利用其 src 属性在服务器和客户端之间创建一条长链接,服务器向 iframe 传输数据(通常是 HTML,内有负责插入信息的 javascript),来实时更新页面。从技术上来讲,两种常见的流技术包括 Forever Iframe(或者 hidden IFrame),或是被用来在 JavaScript 中创建 Ajax 请求的XMLHttpRequest对象的多部分 (multi-part) 特性。
基于流方式的服务器推模型
从上图可以看出,每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。
iframe流方式的优点是浏览器兼容好,但没有方法来实现可靠的错误处理或跟踪连接的 状态,且有些在缓冲方面有问题。
- 优点:消息能够实时到达。这种方式每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。
- 缺点:服务器维持着长连接期间会消耗资源。IE、Morzilla Firefox 下端的进度栏都会显示加载没有完成,而且 IE 上方的图标会不停的转动,表示加载正在进行。
Google 的天才们使用一个称为“htmlfile”的 ActiveX 解决了在 IE 中的加载显示问题,并将这种方法用到了 gmail+gtalk 产品中。Alex Russell 在 “What else is burried down in the depth’s of Google’s amazing JavaScript?”文章中介绍了这种方法。Zeitoun 网站提供的 comet-iframe.tar.gz,封装了一个基于 iframe 和 htmlfile 的 JavaScript comet 对象,支持 IE、Mozilla Firefox 浏览器,可以作为参考。
Comet的优缺点:
优点: 实时性好(消息延时小);性能好(能支持大量用户)
缺点: 长期占用连接,丧失了无状态高并发的特点。
Comet实现框架:
1. Dojo CometD —— http://cometdproject.dojotoolkit.org/
2. DWR —— http://directwebremoting.org/dwr/index.html
3. ICEfaces —— http://www.icefaces.org/main/home/
4. GlassFish Grizzly —— https://grizzly.dev.java.net/
CometD 目前实现 Comet 比较成熟, DWR 弱一些。 ICEfaces 更商业化,实现得很成熟。 Grizzly 是基于GlassFish ,也很成熟。CometD, DWR 开源性好。
CometD
CometD 框架是基于 HTTP 的事件驱动通信解决方案,使用了Bayeux通信协议,提供了一个 Java 服务器部件和一个 Java 客户端部件,还有一个基于 jQuery 和 Dojo 的 JavaScript 客户端库。
Bayeux 通信协议主要是基于 HTTP,提供了客户端与服务器之间的响应性双向异步通信。Bayeux 协议基于通道进行通信,通过该通道从客户端到服务器、从服务器到客户端或从客户端到客户端(但是是通过服务器)路由和发送消息。Bayeux 是一种 “发布- 订阅” 协议。
CometD 与三个传输协议绑定在一起:JSON、JSONP 和 WebSocket。他们都依赖于 Jetty Continuations 和 Jetty WebSocket API。在默认情况下,可以在 Jetty 6、Jetty 7、和 Jetty 8 中以及其他所有支持 Servlet 3.0 Specification 的服务中使用 CometD。
服务器和内部构件
Atmosphere框架
Atmosphere提供了一个通用 API,以便使用许多 Web 服务器(包括 Tomcat、Jetty、GlassFish、Weblogic、Grizzly、JBossWeb、JBoss 和 Resin)的 Comet 和 WebSocket 特性。它支持任何支持 Servlet 3.0 Specification 的 Web 服务器。
Atmosphere 提供了一个 jQuery 客户端库,该库可以使连接设置变得更容易,它能够自动检测可以使用的最佳传输协议(WebSockets 或 CometD)。Atmosphere 的 jQuery 插件的用法与 HTML5 WebSockets API 相似。
Pushlet
Pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。
Pushlet 最后更新于2010年2月5号,之后至今没有再更新。
Cometd 和Atmosphere框架参见示例代码 (https://github.com/brucefengnju/cometdatoms)。
Bayeux 是一套基于 Publish / Subscribe 模式,以 JSON 格式在浏览器与服务器之间传输事件的通信协议。该协议规定了浏览器与服务器之问的双向通信机制,克服了传统 Web 通信模式的缺点。
Bayeux 协议主要基于 HTTP 来传输低延迟的、异步的事件消息。这些消息通过频道 (Channels) 来投递,能够实现从服务器端到客户端、从客户端到服务器端或者通过服务器从一个客户端到另一个客户端的传送。Bayeux 协议的主要目的是为使用了 Ajax 和 Comet 技术的 Web 客户端实现高响应的用户交互。Bayeux 协议旨在通过允许执行者更容易的实现互操作性,来降低开发 Comet 应用程序的复杂性。它解决了共同的消息发布和路由问题,并提供了渐进式的改进和扩展机制。
Dojo 已经对 Cometd 做了封装,基于 Dojo 的 Cometd 包,我们不用再浪费大量的代码在搭建 Cometd 框架上。对于前端脚本代码,我们只需要加上一个 Cometd 包的简单接口代码,便可以开始加入我们自己的业务逻辑代码了。
Comet实现要点:
不要在同一客户端同时使用超过两个的 HTTP 长连接
我们使用 IE 下载文件时会有这样的体验,从同一个 Web 服务器下载文件,最多只能有两个文件同时被下载。第三个文件的下载会被阻塞,直到前面下载的文件下载完毕。这是因为 HTTP 1.1 规范中规定,客户端不应该与服务器端建立超过两个的 HTTP 连接, 新的连接会被阻塞。而 IE 在实现中严格遵守了这种规定。
HTTP 1.1 对两个长连接的限制,会对使用了长连接的 Web 应用带来如下现象:在客户端如果打开超过两个的 IE 窗口去访问同一个使用了长连接的 Web 服务器,第三个 IE 窗口的 HTTP 请求被前两个窗口的长连接阻塞。所以在开发长连接的应用时, 必须注意在使用了多个 frame 的页面中,不要为每个 frame 的页面都建立一个 HTTP 长连接,这样会阻塞其它的 HTTP 请求,在设计上考虑让多个 frame 的src共用一个长连接。
服务器端的性能和可扩展性
一般 Web 服务器会为每个连接创建一个线程,如果在大型的商业应用中使用 Comet,服务器端需要维护大量并发的长连接。在这种应用背景下,服务器端需要考虑负载均衡和集群技术;或是在服务器端为长连接作一些改进。
应用和技术的发展总是带来新的需求,从而推动新技术的发展。HTTP 1.1 与 1.0 规范有一个很大的不同:1.0 规范下服务器在处理完每个 Get/Post 请求后会关闭套接口连接; 而 1.1 规范下服务器会保持这个连接,在处理两个请求的间隔时间里,这个连接处于空闲状态。 Java 1.4 引入了支持异步 IO 的 java.nio 包。当连接处于空闲时,为这个连接分配的线程资源会返还到线程池,可以供新的连接使用;当原来处于空闲的连接的客户发出新的请求,会从线程池里分配一个线程资源处理这个请求。 这种技术在连接处于空闲的机率较高、并发连接数目很多的场景下对于降低服务器的资源负载非常有效。
但是 AJAX 的应用使请求的出现变得频繁,而 Comet 则会长时间占用一个连接,上述的服务器模型在新的应用背景下会变得非常低效,线程池里有限的线程数甚至可能会阻塞新的连接。Jetty 6 Web 服务器针对 AJAX、Comet 应用的特点进行了很多创新的改进。
控制信息与数据信息使用不同的 HTTP 连接
使用长连接时,存在一个很常见的场景:客户端网页需要关闭,而服务器端还处在读取数据的堵塞状态,客户端需要及时通知服务器端关闭数据连接。服务器在收到关闭请求后首先要从读取数据的阻塞状态唤醒,然后释放为这个客户端分配的资源,再关闭连接。所以在设计上,我们需要使客户端的控制请求和数据请求使用不同的 HTTP 连接,才能使控制请求不会被阻塞。
在实现上,如果是基于 iframe 流方式的长连接,客户端页面需要使用两个 iframe,一个是控制帧,用于往服务器端发送控制请求,控制请求能很快收到响应,不会被堵塞;一个是显示帧,用于往服务器端发送长连接请求。如果是基于 AJAX 的长轮询方式,客户端可以异步地发出一个 XMLHttpRequest 请求,通知服务器端关闭数据连接。
在客户和服务器之间保持“心跳”信息
在浏览器与服务器之间维持一个长连接会为通信带来一些不确定性:因为数据传输是随机的,客户端不知道何时服务器才有数据传送。服务器端需要确保当客户端不再工作时,释放为这个客户端分配的资源,防止内存泄漏。因此需要一种机制使双方知道大家都在正常运行。在实现上:服务器端在阻塞读时会设置一个时限,超时后阻塞读调用会返回,同时发给客户端没有新数据到达的心跳信息。此时如果客户端已经关闭,服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。如果客户端使用的是基于 AJAX 的长轮询方式,服务器端返回数据、关闭连接后,经过某个时限没有收到客户端的再次请求,会认为客户端不能正常工作,会释放为这个客户端分配、维护的资源。当服务器处理信息出现异常情况,需要发送错误信息通知客户端,同时释放资源、关闭连接。
我们知道HTTP请求其实是基于TCP连接实现的(也有少部分基于UDP),再看看之前说的HTTP请求处理过程:
- 客户端与服务器建立TCP连接
- 服务器根据客户端提交的报文处理并生成HTML文本
- 将HTML封闭成为HTTP协议报文并返回给客户端
- 关闭链接。
看到这个处理过程,我们不难联想到,如果把第4步——关闭连接给省掉,那不就相当于有一个长连接一直被维持住了么。通过对服务端的一些操作,我们可以直接将数据从这个TCP连接发送客户端了。
通过这种技术,我们可以大大提高服务器推送的实时性,还可以减去服务端不停地建立、施放连接所形成的开销。
目前市面上有不少基于AJAX实现的Comet机制,但主要有两种方式:
- 建立连接后依然使用"询问"+"应答"的模式。虽然工作方式没变,但是因为使用长连接,减去了每次建立与施放连接的工作,所以性能上提升了很多。而且服务器对TCP连接可以有上下文的定义,而不像以前的AJAX完全是无状态的。
- 通过对Stream的写入实现服务器将数据主动发送到客户端。因为是TCP连接,所以通过对服务器的编程,我们可以主动的把数据从服务端发送给客户端,从模式上真正建立起了推送的概念。
总结:基本实现了的服务器主动推送消息。服务器可以随时可以推送数据给客户端,但需要保持一个长连接,浪费资源。
2.1.3 Server-Sent
Server-Sent是HTML5提出一个标准,它延用了Comet的思路,并对其进行了一些规范。使得Comet这项技术由原来的分支衍生技术转成了正统的官方标准。Server-Sent Events包含新的HTML元素EventSource和新的MIME类型 text/event-stream,这个MIME类型定义了事件框架格式。Server-Sent Event是基于HTTP streaming的。response会一直打开,当服务器端有事件发生的时候,事件会被写入response中。
Server-sent-events(SSE)让服务端可以向客户端流式发送文本消息,在实现上,客户端浏览器中增加EventSource对象,使其能通过事件的方式接收到服务器推送的消息,在服务端,使用长连接的事件流协议,即请求响应时增加新数据流数据格式。非常适应于后端数据更新频繁且对实时性要求较高而又不需要客户端向服务端通信的场景下。
它的原理与Comet相同,由客户端发起与服务器之间创建TCP连接,然后并维持这个连接,至到客户端或服务器中的做任何一放断开,ServerSent使用的是"问"+"答"的机制,连接创建后浏览器会周期性地发送消息至服务器询问,是否有自己的消息。本质还是轮询,只不过维持一个长连接,在长连接里进行轮询。
这项标准不仅要求了支持的浏览器能够原生态的创建与服务器的长连接,更要求了对JavaScript脚本的统一性,使得兼程该功能的浏览器可以使用同一套代码完成Server-Sent的编码工作。
优点
- 基于现有http协议,实现简单
- 断开后自动重连,并可设置重连超时
- 派发任意事件
- 跨域并有相应的安全过滤
缺点
- 只能单向通信,服务器端向客户端推送事件
- 事件流协议只能传输UTF-8数据,不支持二进制流。
- IE下目前不支持EventSource
- 如果代理服务器或中间设备不支持SSE,会导致连接失效,正式环境中使用通过TLS协议发送SSE事件流。
总结:总得来说SeverSent就是HTML5规范下的Comet,具有更好的统一性,而且简单好用。严格来说,技术本身没有什么改进。
过渡:
插件提供 socket 方式:比如利用 Flash XMLSocket,Java Applet 套接口,Activex 包装的 socket。
- 优点:原生 socket 的支持,和 PC 端和移动端的实现方式相似。
- 缺点:浏览器端需要装相应的插件。
2.1.4 WebSocket
Comet和SSE仍然是单向通信(服务器向客户端),不能适应Web应用的飞速发展。于是,各种新技术不断涌现,其中WebSocket在Google的力推之下已经成为业界标准,并不断完善中。其基于TCP之上定义了帧协议,支持双向的通信。
WebSocket是一种全新的协议,不属于http无状态协议,协议名为ws协议或wss协议。ws不是http,所以传统的web服务器不一定支持。一个websocket连接地址会是这样的写法:ws://wilsonliu.cn:8080/webSocketServer。
WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket通讯协议于2011年被IETF定为标准RFC 6455,WebSocket API被W3C定为标准。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。
看名字就知道了,这是一个可以用在浏览器上的Socket连接。这也是一个HTML5标准中的一项内容,他要求浏览器可以通过JavaScript脚本手动创建一个TCP连接与服务端进行通讯。因为Socket是一个全双工的TCP和UDP连接,所以不需要轮询,只需要保持长连接和心跳包。通信协议的header也很小,相比与之前的long poll, web socket 能够更好的节省服务器资源和宽带并达到实时通信。
WebSocket通信协议包含两个部分,一是开放性HTTP握手连接协商连接参数,二是二进制消息分帧机制(接收消息的文本和二进制数据传输)。它是一个独立完善的协议,也可以在浏览器之外实现。
另外WebSocket使用了ws和wss协议(表示使用加密信道通信(TCP + TLS),支持接收和发送文本和二进制数据),需要服务器有与之握手的算法才能将连接打开。
所以WebSocket相对于之前几种手段来说,其编码量是最大的(Socket的编码量就是大),但由于没有其它的约束,因此它也可以自由地实现所有可能的功能。
即可以满足"问"+"答"的响应机制,也可以实现主动推送的功能。与ServerSent相同,HTML5也对WebSocket调用的JavaScript进行规范。WebSocket具有较为复杂的协议,需要在服务端做额外编程才能进行数据通讯。
总结:将Socket的建立移植到了浏览器的脚本端,JS可以创建连接与服务器通讯,包括请求-响应模型,主动轮询的推送模式等。技术本身没有什么改进。最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
服务端实现
- Node (Socket.IO, WebSocket-Node, ws)
- Java (Jetty)
- Python (Tornado, pywebsocket)
- swoole:官方写的websocket php客户端 https://github.com/swoole/swoole-src/edit/master/examples/websocket/WebSocketClient.php
- ...
使用场景:
适合于对数据的实时性要求比较强的场景,如通信、股票、Feed、直播、共享桌面,特别适合于客户端与服务频繁交互的情况下,如实时共享、多人协作等平台。
优点:
- 真正的全双工通信
- 支持跨域设置(Access-Control-Allow-Origin)
- 更好的节省服务器资源和带宽并达到实时通讯
缺点
- 采用新的协议,后端需要单独实现
- 客户端并不是所有浏览器都支持,目前还未普及,浏览器支持不好
- 代理服务器会有不支持websocket的情况
- 无超时处理
- 更耗电及占用资源
代理和很多现有的HTTP中间设备可能不理解新的WebSocket协议,而这可能导致各种问题,使用时需要注意,可以使借助TLS,通过建立一条端到端的加密信道,可以让WebSocket通信绕过所有中间代理。
HTML5 WebSocket 设计出来的目的就是要取代轮询和 Comet 技术,使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。浏览器向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及 Comet 技术比较,具有很大的性能优势。
使用方法:
WebSocket 协议本质上是一个基于 TCP 协议。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息 ”Upgrade: WebSocket” 表明这是一个申请协议升级的 HTTP 请求(详细的 WebSocket 消息的内容这里就不详细说了,基本和 HTTP 的差不多,而且都是由 WebSocket 对象自动发送和接收的,对用户透明),服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
WebSocket API 最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。WebSocket 并不限于以 Ajax (或 XmlHttpRequest)方式通信,因为 Ajax 技术需要客户端发起请求,而 WebSocket 服务器和客户端可以彼此相互推送信息;XmlHttpRequest 通信受到域的限制,而 WebSocket 允许跨域通信。
需要注意的问题是,除了安全和性能以外,服务端只管往 socket 里面写数据就可以了,WebSocket 的通信数据全部是以 ”\x00″ 开头以 ”\xFF” 结尾的,无论是服务端发出的数据还是客户端发送的数据都遵从这个格式,唯一不同的是客户端的 WebSocket 对象能够自动将头尾去除,获得主体数据,这就省却了在客户端处理原始数据的必要,而且 WebSocket 通信的消息总是 UTF-8 格式的。
应用场景:
WebSocket广泛应用于社交聊天、直播、弹幕、多玩家游戏、协同编辑、股票基金实时报价、体育实况更新、视频会议/聊天、基于位置的应用、在线教育、智能家居等需要高实时的场景。
WebSocket特点:
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
- 能实现真正意义上的数据推送。
2.1.5 WebSocket + MessageQueue
MessageQueue,简称MQ,也就是消息队列。是一种常常用于TCP服务端的技术。通过生产和访问各种消息类型,MQ服务器会将生产者所生成的消息发给感兴趣的客户端。市面上有很多的MQ框架,比如:ActiveMQ。本质上是一种发布-订阅模式。
ActiveMQ已经支持了WebSocket协议,也就意味着,WebSocket已经可以作为一个生产者或一个消费者,与MQ服务器连接。
开发者可以通过MQTT的JS脚本,连接上MQ服务器,同时将Web服务器也连上MQ服务器,从此可以告别了HTTP通讯协议,完完全全使用Socket通讯来完成数据的交换。
Java消息服务(Java Message Service)是message数据传输的典型的实现方式,是一种发布-订阅模式。系统A和系统B通过一个消息服务器进行数据交换。系统A发送消息到消息服务器,如果系统B订阅系统A发送过来的消息,消息服务器会消息推送给B。双方约定消息格式即可。目前市场上有很多开源的jms消息中间件,比如 ActiveMQ, OpenJMS 。
2.2 C/S架构
C/S架构主要指服务器与应用之间的通信架构,比如服务器与Android App之间的通信,不包括基于H5的移动App。
其实上述的B/S架构所用的六种方法可以移植或者借鉴到C/S架构。所以下述的几种方法都是C/S架构特有的方法。
2.2.1 反向连接
如果不是B/S架构,可以交换C/S身份,进行连接。我们称之为反向/反弹连接,有点类似于反弹木马的行为方式。此时(原服务器端)客户端向服务器端(原客户端端)发送通信数据包。
此时要求服务器有公网IP,并且FW不限制连接建立。
2.2.2 SMS(Push)方式
SMS(Push)方式:通过拦截SMS消息并且解析消息内容来了解服务器的命令,但这种方式一般用户在经济上很难承受。
2.2.3 现有的推送方案
A、C2DM云端推送方案
在Android手机平台上,Google提供了C2DM(Cloud to Device Messaging)服务。Android Cloud to Device Messaging (C2DM)是一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。
总结:服务器发送通知给C2DM服务器,C2DM服务器发送通知给Android系统的C2DM服务,C2DM服务通知移动应用主动连接服务器,以获取服务器发送的数据。
该方案存在的主要问题是C2DM需要依赖于Google官方提供的C2DM服务器,由于国内的网络环境,这个服务经常不可用。
B、MQTT协议实现Android推送
采用MQTT协议实现Android推送功能也是一种解决方案(https://legacy.gitbook.com/book/mcxiaoke/mqtt-cn/details)。MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。 wmqtt.jar 是IBM提供的MQTT协议的实现。我们可以从这里(https://github.com/tokudu/AndroidPushNotificationsDemo)下载该项目的实例代码,并且可以找到一个采用PHP书写的服务器端实现(https://github.com/tokudu/PhpMQTTClient)。
C、RSMB实现推送功能
Really Small Message Broker (RSMB) ,是一个简单的MQTT代理,同样由IBM提供,其查看地址是:http://www.alphaworks.ibm.com/tech/rsmb。缺省打开1883端口,应用程序当中,它负责接收来自服务器的消息并将其转发给指定的移动设备。SAM是一个针对MQTT写的PHP库。我们可以从这个http://pecl.php.net/package/sam/download/0.2.0地址下载它。
D、XMPP协议实现Android推送
Google官方的C2DM服务器底层也是采用XMPP协议进行的封装。XMPP(可扩展通讯和表示协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线探测。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息。
androidpn是一个基于XMPP协议的java开源Android push notification实现。它包含了完整的客户端和服务器端。但也存在一些不足之处:
1) 比如时间过长时,就再也收不到推送的信息了。
2)性能上也不够稳定。
3)将消息从服务器上推送出去,服务器就不再管理了,不管消息是否成功到达客户端手机上。
如果我们要使用androidpn,则还需要做大量的工作,需要理解XMPP协议、理解Androidpn的实现机制,需要调试内部存在的BUG。
E、使用第三方平台
目前国内、国外有一些推送平台可供使用,但是涉及到收费问题、保密问题、服务质量问题、扩展问题等等。百度云,个推等。
实际上,主流的移动平台都已经有系统级的推送产品,Android上有GCM(Google Cloud Messaging),iOS上有APNS(Apple Push Notification service),WinPhone有MPNS(Microsoft Push Notification service)。但GCM在国内处于不可用状态(一、国内大部分Android手机都不带Google服务,也就用不了GCM,这是主要的问题。 二、在国内Google的服务一般都不太稳定),所以国内的移动应用采用另外一种做法---在后台运行一个Service,维持应用于服务端的TCP长连接,以达到实时消息送达的效果。
但是在移动端如何稳定的维持长连接是一件非常复杂的事情,前面说了,客户端通过定时发送心跳信号(Heartbeat)以维持与服务端的长连接,但是,如果心跳的频率太频繁,移动设备耗电增加,心跳间隔太久又可能使得连接被断开。并且普遍认为移动设备处于一个多变的网络环境中,WIFI,2G,4G切换,基站切换都会引起网络变动,在不同网络环境下的心跳频率,与网络变动的重连动作,都需要大量的数据统计分析总结出来。
这仅仅是客户端的难题,在如今移动应用动辄成百上千的用户量的情况下,如何维护如此多的长连接,如果应对大规模的消息下发以及后续针对下发消息的各种统计动作都是技术难点。
再者,现在应用一般都是全平台的,发送一条消息,应该同时发送给Android,iOS, WinPhone,Android端走自建的TCP长连接通道,iOS与WinPhone走自家的系统推送通道。那么意味着你服务端要维护这三套推送系统。
显然对于小团队,要独自建立一套消息推送系统的难度非常大,所以市场上涌现出很多优秀的推送产品,帮开发者聚合这些推送方式,并提供统一的推送接口。国外如 Urban Airship, Parse等, 国内有JPush,百度云推送,信鸽,LeanCloud等。(比较遗憾的是,非常优秀的Parse已经被Facebook宣布停止开发,并将于1年后关闭)
现在除了体量非常大的公司自建推送系统外,一般普通公司都是使用第三方推送服务,以上所有的第三方推送服务,基础功能都是免费的。
移动推送技术
IOS
首先是IOS平台,IOS的推送是通过苹果自己的APNs服务进行的,用户需要将device_token(设备号+包名)以及消息内容等推送信息交给APNs服务器,剩下的均由苹果自己来完成。但是如果提供的device_token是失效的(app被卸载、系统版本升级导致device_token变化等情况)那么推送过程就会被中断,频繁的断线重连甚至会被APNs认为是一直DoS攻击。
对于iOS App来说,一般情况下(有例外情况),App不允许后台长期运行。所以当App被系统挂起或杀掉后,推送只能通过APNS送达。APNS通知到达手机后,由用户唤起App,App运行起来后,就能够启动自己的长连接(如XMPP),完成进一步的推送。当然,APNS的推送速度肯定比不上自己的长连接,所以一般iOS上还一般采用伪推送的方式。App在退到后台后能短暂执行几分钟,长连接也会持续一段时间。如果在这段时间内推送可以通过自己的长连接到达,那么App在客户端产生一个本地推送(类似于系统产生的远程推送)。
图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider。
APNS 是Apple Push Notification Service(Apple Push服务器)的缩写,是苹果的服务器。
上图可以分为三个阶段。
第一阶段:.net应用程序把要发送的消息、目的iPhone的标识打包,发给APNS。
第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发到iPhone。
第三阶段:iPhone把发来的消息传递给相应的应用程序, 并且按照设定弹出Push通知。
iOS 系统的推送(APNS,即 Apple Push Notification Service)依托一个或几个系统常驻进程运作,是全局的(接管所有应用的消息推送),所以可看作是独立于应用之外,而且是设备和苹果服务器之间的通讯,而非应用的提供商服务器。你的例子里面,腾讯 QQ 的服务器(Provider)会给苹果公司对应的服务器(APNs)发出通知,然后再中转传送到你的设备(Devices)之上。当你接收到通知,打开应用,才开始从腾讯服务器接收数据,跟你之前看到通知里内容一样,但却是经由两个不同的通道而来。
Android
而在Android上,跟APNS对应的服务GCM,在国内无法使用。而各个手机厂商(如小米)自己的推送服务又无法覆盖所有手机终端。所以,在Android上实现推送只能自己启动Service维持一个长连接。正是因为安卓上很多应用都有后台长连接挂着,所以安卓手机更费流量,也更费电。Android平台在不使用GCM的情况下就需要将自己的服务器或是第三方推送服务提供商的服务器与设备建立一条长连接,通过长连接进行推送。但是不建议自己设置服务器实现推送功能,一是因为成本太高(开发成本、维护成本),自己搭建的服务器无论是稳定性还是速度上都比不了第三方推送服务提供商的效果。另一个是因为自己的数据量较小,使用第三方推送服务提供商可以用他们的维度进行推送,实现精准推送。友盟推送就是做的比较好的,可以根据用户分群、地区、语言等多维度进行推送,最大程度减少对于用户的干扰,仅把消息推送给相关用户。
开发者通过第三方推送服务提供商将信息直接下发给需要的设备,第三方推送服务提供商与设备建立一条长连接通道,并且将消息路由到APP中(图中的设备1与设备2),对于像设备3这种无网络连接或是没有成功建立长连接通道的设备,会在设备3连网且推送消息没有过期的情况下自动收到由第三方推送服务提供商推送过来的消息,保证消息不会丢失。
3 详细介绍
分析来看,以上介绍的服务器主动推送技术主要分为:
3.1 短连接轮询
前端用定时器,每间隔一段时间发送请求来获取数据是否更新,这种方式可兼容ie和支持高级浏览器。通常采取setInterval或者setTimeout实现。
通过递归的方法,在获取到数据后每隔一定时间再次发送请求,这样虽然无法保证两次请求间隔为指定时间(数据的生成时间),但是获取的数据顺序得到保证。
缺点:
1、页面会出现‘假死’
setTimeout在等到每次EventLoop时,都要判断是否到指定时间,直到时间到再执行函数,一旦遇到页面有大量任务或者返回时间特别耗时,页面就会出现‘假死’,无法响应用户行为。
2、无谓的网络传输
当客户端按固定频率向服务器发起请求,数据可能并没有更新,浪费服务器资源。特别是每次轮询时候的握手,挥手建立连接。
3.2 长轮询
客户端像传统轮询一样从服务端请求数据,服务端会阻塞请求不会立刻返回,直到有数据或超时才返回给客户端,然后关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
长轮询解决了频繁的网络请求,建立多次连接,浪费服务器资源,并且可以及时返回给浏览器。
缺点:
1、保持连接也会消耗资源,占用带宽。
2、若果服务器没有返回有效数据,程序超时。
3.3 iframe流
iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。
- 前端实现步骤:
1、Iframe设置为不显示。
2、src设为请求的数据地址。
3、定义个父级函数,让用户iframe子页面调用传数据给父页面。
4、定义onload事件,服务器timeout后再次重新加载iframe。
- 后端输出内容:
当有新消息时服务端会向iframe中输入一段js代码.:println("<script>父级函数('" + 数据 +"<br>')</script>”);用于调用父级函数传数据。
- 优点:
iframe流方式的优点是浏览器兼容好,Google公司在一些产品中使用了iframe流,如Google Talk。
- 缺点:
1、IE、Mozilla Firefox会显示加载没有完成,图标会不停旋转。
2、服务器维护一个长连接会增加开销。
3.4 WebSocket
WebSocket是一种全新的协议,随着HTML5草案的不断完善,越来越多的现代浏览器开始全面支持WebSocket技术了,它将TCP的Socket(套接字)应用在了webpage上,从而使通信双方建立起一个保持在活动状态连接通道。
运行流程:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。
JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356。
- 原理:
WebSocket协议是借用HTTP协议的101 switch protocol(服务器根据客户端的指定,将协议转换成为 Upgrade首部所列的协议)来达到协议转换的,从HTTP协议切换成WebSocket通信协议。
- 具体连接方式:
通过在请求头中增加 upgrade:websocket 及通信密钥(Sec-WebSocket-Key),使双方握手成功,建立全双工通信。
WebSocket客户端连接报文:
WebSocket服务端响应报文:
- 通信过程:
websocket是纯事件驱动的,一旦 WebSocket 连接建立后,通过监听事件可以处理到来的数据和改变的连接状态。数据都以帧序列的形式传输。服务端发送数据后,消息和事件会异步到达。WebSocket编程遵循一个异步编程模型,只需要对WebSocket对象增加回调函数就可以监听事件。
(websocket示意图)
3.5 Server-sent Events(SSE)
sse与长轮询机制类似,区别是每个连接不只发送一个消息。客户端发送一个请求,服务端保持这个连接直到有新消息发送回客户端,仍然保持着连接,这样连接就可以再次发送消息的,由服务器单向发送给客户端。
原理:
SSE本质是发送的不是一次性的数据包,而是一个数据流。可以使用 HTTP 301 和 307 重定向与正常的 HTTP 请求一样。服务端连续不断的发送,客户端不会关闭连接,如果连接断开,浏览器会尝试重新连接。如果连接被关闭,客户端可以被告知使用 HTTP 204 无内容响应代码停止重新连接。
SSE只适用于高级浏览器,ie不支持。因为ie上的XMLHttpRequest对象不支持获取部分的响应内容,只有在响应完成之后才能获取其内容。
| 短轮询 | 长轮询 | Websocket | sse |
---|---|---|---|---|
通讯方式 | http | http | 基于TCP长连接通讯 | http |
触发方式 | 轮询 | 轮询 | 事件 | 事件 |
优点 | 兼容性好容错性强,实现简单 |
| 全双工通讯协议,性能开销小、安全性高,有一定可扩展性 | 实现简便,开发成本低 |
缺点 | 安全性差,占较多的内存资源与请求数 | 安全性差,占较多的内存资源与请求数 | 传输数据需要进行二次解析,增加开发成本及难度 | 只适用高级浏览器 |
适用范围 | b/s服务 | b/s服务 | 网络游戏、银行交互和支付 | 服务端到客户端单向推送 |
TCP是全双工通讯,也就是只要知道双方socket,双方都可以发送数据。
sse | websocket | 轮询 | |
服务器部署 | × | √ | × |
浏览器兼容性 | × | × | √ |
后端推送 | √ | √ | × |
4 总结
4.1 技术总结
B/S架构
1、AJAX的(短)轮询。(PULL拉取)
2、Comet和SSE利用长轮询和长连接,基本实现了服务器主动推送。但其本质上依旧是基于请求-响应模式,但对实现和服务器端要求不高,技术变化不大。(PUSH推送)
以上俩种方法都不是真正的主动推送技术。
3、WebSocket通过建立全双工通信的Socket,实现了真正的服务器推送功能。对服务器和编码要求较高。(PUSH推)
4、发布-订阅模式,需要专门的服务器。
C/S架构
1、如果不是B/S架构,可以交换C/S身份,进行连接。我们称之为反向/反弹连接。此时客户端发送轮询数据包,进行周期性询问和请求。类似于反弹木马的行为方式。
2、SMS(Push)方式:通过拦截SMS消息并且解析消息内容来了解服务器的命令,但这种方式一般用户在经济上很难承受。
4.2 经验总结
(1)总得来说,在HTML5规范下,最推荐使用ServerSent和WebSocket的方式进行服务器消息的推送。
对比这两种方式:
ServerSent的方式,可以使服务端的开发依然依用以前的方式,但是其工作方式与Comet类似。特别是对于老项目的翻新,还是用SeverSent比较好。
而WebSocket的方式,则对服务端的开发有着较高的要求,但其工作方式是完全的推送。真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。
(2)
对于简单的推送需求又不考虑兼容低版本浏览器,推荐使用server-sent Events。
如果需要多条双向数据实时交互或需要二进制传输,推荐websocket。
对于还要考虑低版本浏览器,那么还是用轮询(基本AJAX)来实现功能。
(3)
IM、实时对战游戏之类的应用场景,就一定需要长连接推送。像天气类,新闻类 app,对推送实时性要求并不太高,采取定期拉取策略也是可以的(实际上实现难度要降低几个数量级)。
(4)
考虑问题:
(1)采用什么协议?XMPP还是MQTT还是自定义二进制协议?是否像微信一样,需要推送二进制数据(比如短语音和缩略图数据)?
(2)如何保证后台长连接不死?像大公司的各个App,普通会采用互拉的手段保持后台运行。而最牛的还是微信,在各个手机系统上基本都算是“开挂”了。
(3)如何做才能真正不丢数据?涉及到系统的方方面面,比如消息的确认,客户端和服务器的数据同步,客户端的数据存储的事务保证,后台消息队列如何设计保证不丢数据。如果是IM,离线数据如何处理?
(4)长连接的Keep Alive和连接状态的检测。比如XMPP相当于一个永远解析不完的XML流,使用一个空格作为Keep Alive消息。
(5)在iOS上进行伪推送。前面已经提到过。还有如何利用iOS特性,用特殊APNS推送唤起App预先下载消息。
(6)长连接的安全性。验证以及加密。
(7)是用各种云推送还是自己实现?用哪家云推送更好?
实现方法:
1. html5 websocket
2. WebSocket 通过 Flash
3. XHR长时间连接
4. XHR Multipart Streaming
5. 不可见的Iframe
6. <script>标签的长时间连接(可跨域)
(4)基础实现;提供的推送系统API;第三方推送服务平台。
(5)nodejs的http://socket.io,它是websocket的一个开源实现,对不支持websocket的浏览器降级成comet / ajax 轮询,http://socket.io的良好封装使代码编写非常容易。
4.3 实例
同步会议和即时通信是推送服务的典型事例。聊天信息和临时文件一旦被发送,用户就会通过推送服务接收到。分离的 peer-to-peer 程序(例如 WASTE )和集中的程序(例如 IRC 或 XMPP )都允许推送文件,这就意味着发件人开始进行数据的传送,而不是收件人。
Email 可能也是一个推送系统:SMTP 协议是一个推送协议(见 Push e-mail)。然而,从邮件服务器到桌面计算机的最后一步通常使用的是像 POP3 或 IMAP 这样的 pull 协议。现代电子邮件客户端通过反复轮询邮件服务器,使这一步看起来是瞬间的,它经常检查是否有新邮件。IMAP 协议包括 IDLE command,它允许服务器当新邮件到达时向客户端发消息。初代的黑莓是第一个在无线环境下推送电子邮件的设备,使得它成为当今的佳话。
其他例子是 PointCast Network (点播式网络),它在二十世纪九十年代具有非常广泛的应用。它通过屏保推送新闻和股票行情。在浏览器战争的巅峰时期,Netscape和 Microsoft 通过在他们的软件里集成频道定义格式(CDF)推送技术,但是那时并没有多少人关注。 CDF 消失了并移出了浏览器的时代,取而代之的是2000年的 RSS(一种下拉式系统)。
4.3.1 网页推送
这个网络工程师极力推崇的网页推送方案是一个简单的协议,通过使用 HTTPv2.0 版本的去即时的事件。例如来电或留言,能够被即时的传达(或者说推送)出去。这个协议将所有即时的事件都合并到一个简单的会话中,其中这个会话可以确保不管在网络还是音频资源上都有很好的使用效率。这一个简单的服务包含了所有的事件,并在当它到达分发给对应的应用所有的事件。这个请求只需要一个会话,避免了重复发送这样高昂的成本。
4.3.2 HTTP服务器推送
HTTP 服务器推送(也称为 HTTP 流)是一种将未经请求的(异步)数据从Web服务器发送到Web浏览器的机制。任何一种机制都可以实现 HTTP 服务的推送。
一些 HTML5 的 WebSocket API 允许 Web 服务器和客户端,通过 TCP 全双工通信进行交流。
一般情况下,当响应完毕一个来自客户端的请求之后,Web 服务器不会去终止一个连接。 Web 服务器使连接打开,以便当发生事件(例如,需要向一个或多个客户端报告的内部数据的变化)时,可以立即发送它;否则,该事件必须排队,直到接收到客户端的下一个请求为止。大多数 Web 服务器都通过 CGI 提供这种功能(例如,Apache HTTP 服务器上未解析的报头脚本)。这是一种靠分块传输编码方法的基本机制。
另一种机制关系到一个特殊的 MIME 类型,称为 multipart/x-mixed-replace,这是由 Netscape 在1995年引入的。当服务器想向客户端推出新版本时,Web 浏览器将此理解为一个文件改变。它如今仍然被 Firefox 、 Opera 和 Safari 支持,但它被 Internet Explorer 忽略了。它可以应用于 HTML 文档,以及用于流式传输图像的相机应用。
网页超文本应用技术工作小组(WHATWG)Web Applications 1.0 proposal 包括一个机制来推送内容到客户端。2006年9月1日, Opera Web 浏览器在一个名为“服务器发送事件”的功能中实现了这个新的实验系统。现在它作为 HTML5 的一部分被标准化了。
4.3.3 推送技术
在这个技术,服务器充分利用持久的 HTTP 协议,使得响应永久打开,(即服务器永不关闭响应),有效地去欺骗浏览器保持加载,直到初始页面被认为完全加载完毕之后。然后服务器周期的发送 JavaScript 代码去更新这网页的内容。从而实现推送能力,通过使用这个技术,客户不需要JAVA程序或者其他的插件程序去保持与服务器的连接。客户端也会自动地收到新事件,推送给服务器。这个方法有一个严重的不足,就是缺乏一种控制,服务器已经结束进程使浏览器连接超时;一个页面如果发生连接超时必然会导致其刷新。
4.3.4 长轮询
长轮询本身并不是一个真正的推送;长轮询是传统轮询技术的一种变体,但是它允许在一个真正的推送不可能的情况下模拟推送机制,例如安全策略的网站需要阻止的HTTP/S请求。
长轮询, 客户端从服务器请求信息与正常轮询完全相同,但正如你预料到的,服务器可能不会立即回应。当收到轮询时如果服务器的客户端没有新的信息,不是发送一个空的响应,服务器端公开请求并等待成为有用的响应信息。一旦它获得新的信息,服务器会立即向客户端发送 HTTP/S 的响应,完成开放 HTTP 的请求。收到服务器响应后,客户端通常会立即发出另一个服务器的请求,这样,通常的反应延迟(信息第一次可用到下一个客户端请求的中间时间)与客户端的消除相关。
例如,当这样的连接是困难的或不可能直接采用的(例如,在web浏览器),作为对连续 TCP 连接的长轮询的替代,BOSH 是一个流行的、长寿的 HTTP 技术。在 XMPP是一个潜在的技术,苹果用于 iCloud 的推送支持。
4.3.5 Flash XMLSocket 传达
这项技术被 cbox(Cbox is a chat application for online communities and groups. Get a Cbox, and your visitors and users can engage with one another in real-time conversation.) 和其他聊天应用程序所使用,并在一个单像素 Adobe Flash 电影中使用 XMLSocket 对象。在 JavaScript 控制下,客户端创建一个 TCP 连接到服务器上的单向中继。中继服务器不会从这个套接字读取任何内容,相反它会向客户端发送一个唯一标识符。接下来客户机向 web 服务器发送 HTTP 请求,包括这个标识符。Web 应用程序可以将向客户机发送的消息发送到中继服务器的本地接口,然后中继服务器通过 Flash 套接字传递给客户机。这种方法的优点是,它能鉴别许多 web 应用程序的典型的读写不对称,包括聊天,它还提供了更高的效率。由于它不接受传出套接字的数据,所以中继服务器根本不需要轮着传出 TCP 连接,使其保持数万个并发连接成为可能。在这个模型中扩展的限制是底层服务器操作系统的 TCP 堆栈。
4.3.6 可靠的组数据传送 (RGDD)
例如云计算的服务,增加数据的可靠性和可用性,这通常被推送(被复制)到多台机器上。例如, Hadoop 分布式文件系统(数据库)对所有的储存对象复制两个副本。RGDD 重点研究有效的铸造一个对象从一个位置到多个位置与此同时通过发送极小的数目的副本来节省宽带(在最好的情况下只有一个)在任何跨越网络的对象。例如,数据广播是一个在数据中心传递多个节点,依靠规则和结构化拓扑和 DCCast 是一个类似接近跨过数据中心交付方法的方案
4.3.7 电子邮件
传统的移动邮件客户端可以通过频繁向邮件服务器查询新邮件来模拟邮件推送的用户体验。IMAP协议实际上允许在任意时间发送多个(不包括邮件数据的)通知。IDLE(闲置)就是这样一种功能,它告知服务器可以在任何时候发送通知,并通过客户端运行发送通知以外的命令,从而有效地提供了一个等同于推送的用户体验。
IMAP POP3 都没有推送,只能定时查询。foxmail 这种也是定时去刷新登录的。
除非是一些专有协议:Exchange ActiveSync,Mapi 。
总结:
优点 | 缺点 | |
推/PUSH | 实时性高,管理方便,相对节省流量 | 随时维护一个长连接,浪费电池电量,浪费系统资源 |
拉/PULL | 灵活性高,实现简单,相对节省电量 | 不断地访问服务器,可能带来网络带宽的浪费 |
“推”主要用到的是推送机制,当后台服务器有消息更新时立即发送给应用程序;“拉”主要用到的是轮询机制,应用程序不定时的频繁访问后台服务器以获取最新的消息。
轮询机制是客户端每隔一段时间向服务器发送 请求以确定是否有数据更新,如果有,返回更新数据;否则,返回空。轮询方式
分为两类,普通轮询和长轮询。普通轮询指的是当服务器响应客户端的请求后立即关闭连接,这样可以保证开发的简易性和操作的便捷性,但会带来巨大的资源和带宽浪费。长轮询指的是当服务器响应客户端的请求后并不立即关闭连接,而是保持一段时间,当客户端回复终止包或者连接超时才关闭,这样可以避免无数据更新时的频繁请求,但是会带来服务器资源的浪费。减少了轮询的次数。
推送机制是服务器开启服务后建立持久连接,当服务器有数据更新时直接将其发送到客户端,避免了多次建立连接。
目前,实现 Android 平台信息推送的解决方案主要有四种,基于 GCM 服务,基于 XMPP 协议,基于 MQTT 协议,使用第三方平台(如百度推送,极光推送)。