在各种BS架构的应用程序中,往往都希望服务端能够主动地向客户端推送各种消息,以达到类似于邮件、消息、待办事项等通知。
往BS架构本身存在的问题就是,服务器一直采用的是一问一答的机制。这就意味着如果客户端不主动地向服务器发送消息,服务器就无法得知如何给客户端推送消息。
随着HTML、浏览器等各项技术、标准的发展,依次生成了不同的手段与方法能够实现服务端主动推送消息,它们分别是:AJAX,Comet,ServerSent以及WebSocket。
本篇文章将对上述提及到的各种技术手段进行直白化的解释。
AJAX正常的一个页面在浏览器中是这样工作的:
此时,用户点击了网站上任何一个<a>或触发任何一个<form>提交时:
我们不难发现的是整个过程中间,一旦建立了一个连接,页面就无法再维护住了。整个过程看上去有点强买强卖,也许我只要一杯新的可乐,但你非要给我一整个套餐组合。
此时我们可以了解一下XmlHttpRequest组件,这个组件提供我们手动创建一个HTTP请求,发送我们想要的数据,服务器也可以只返回我们想要的结果,最大的好处是,当我们收到服务器的响应时,原来的页面没有被摧毁。这就好比,我喊一句"我的咖啡喝完了,我要续杯",然后服务员就拿了一杯咖啡过来,而不是会把我没吃完的套餐全部倒掉。
当我们利用AJAX实现服务器推送时,其实质是客户端不停地向服务器询问"有没有给我的消息呀?",然后服务器回答"有"或"没有"来达到的实现效果。它的实现方法也很简单,利用jQuery框架封装好的AJAX调用也很方便:
function getMessage(fn) { $.ajax({ url: "Handler.ashx", //一个能够提供消息的页面 dataType: "text", //响应类型,可以是JSON,XML等其它类型 type: "get", //HTTP请求类型,还可以是post success: function (d, s) { fn(d); //得到了正常的响应时,利用回调函数通知外部 }, complete: function (x, s) { setTimeout(function () { getMessage(fn); }, 5000); //无论响应成功或失败,在若干秒后再询问一次服务器 } }); }
通过上面的代码,可以每隔5秒询问一次服务器是否有需要处理的消息,通过这种方式可以达到推送的效果,但是会存在一个问题:
而且严格地来说,这种实际方式,并不是真正意义上的服务器主动推送消息,但由于早期技术手段缺乏,所以AJAX轮循成为了一种很普遍的手段。
Comet
我们知道HTTP请求其实是基于TCP连接实现的,再看看之前说的HTTP请求处理过程:
看到这个处理过程,我们不难联想到,如果把第4步——关闭连接给省掉,那不就相当于有一个长连接一直被维持住了么。通过对服务端的一些操作,我们可以直接将数据从这个TCP连接发送客户端了。
通过这种技术,我们可以大大提高服务器推送的实时性,还可以减去服务端不停地建立、施放连接所形成的开销。
目前市面上有不少基于AJAX实现的Comet机制,但主要有两种方式:
Server-Sent
Server-Sent是HTML5提出一个标准,它延用了Comet的思路,并对其进行了一些规范。使得Comet这项技术由原来的分支衍生技术转成了正统的官方标准。
它的原理与Comet相同,由客户端发起与服务器之间创建TCP连接,然后并维持这个连接,至到客户端或服务器中的做任何一放断开,ServerSent使用的是"问"+"答"的机制,连接创建后浏览器会周期性地发送消息至服务器询问,是否有自己的消息。
这项标准不仅要求了支持的浏览器能够原生态的创建与服务器的长连接,更要求了对JavaScript脚本的统一性,使得兼程该功能的浏览器可以使用同一套代码完成Server-Sent的编码工作。
创建代码非常简单:
s = new EventSource("Handler.ashx"); //当收到一个非自定义事件时的回调函数 s.onmessage = function (e) { alert(e.data); }; //当收到一个被服务器命名为MyEvent事件消息时的回调函数 s.addEventListener("MyEvent", function (e) { alert(e.data); });
而服务器的代码也很简单:
public class Handler : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = ; context.Response.Expires = -1; context.Response.Write(); //事件类型,使用\r\n结尾 context.Response.Write(); //事件数据,换行时使用\r\n,并在新行再加上data: context.Response.Write(); //事件数据结束,使用\n\n context.Response.Flush(); //这里不能用End,否则是关闭连接的 } public bool IsReusable { get { return true; } } }
两小段代码,就已经具备了服务器消息推送了。
总得来说SeverSent就是HTML5规范下的Comet,具有更好的统一性,而且简单好用。
WebSocket
看名字就知道了,这是一个可以用在浏览器上的Socket连接。
这也是一个HTML5标准中的一项内容,他要求浏览器可以通过JavaScript脚本手动创建一个TCP连接与服务端进行通讯。
WebSocket不包含太多的额外功能,仅仅就是TCP连接的几项基本功能:建立,临时以及发送。
另外WebSocket使用了ws和wss协议,需要服务器有与之握手的算法才能将连接打开。
所以WebSocket相对于之前几种手段来说,其编码量是最大的,但由于没有其它的约束,因此它也可以自由地实现所有可能的功能。
即可以满足"问"+"答"的响应机制,也可以实现主动推送的功能。
与ServerSent相同,HTML5也对WebSocket调用的JavaScript进行规范,我们可以弄过很简单的一代码构建一个WebSocket连接