web server 推送消息


BS架构[browser server]的应用程序 服务端 主动地向客户端推送各种消息 达到类似于邮件 消息 待办事项等通知
BS架构的问题是 服务器采用的是一问答机制
如果客户端不主动地向服务器发送消息 服务器无法得知如何给客户端推送消息
浏览器用不同的手段与方法 实现服务端主动推送消息 AJAX Comet ServerSent 及 WebSocket
AJAX
正常浏览器这样工作
1 用户向给予浏览器访问的地址
2 浏览器根据地址访问服务器 并与服务器之间创建一个TCP连接(HTTP请求)
3 服务器根据地址和其它数据 组建一段HTML文本 将写入TCP连接 然后关闭连接
4 浏览器得到了来自服务器的HTML文本 解析并呈现 浏览器上给用户浏览
用户点击网站上<a>或触发<form>提交
1 浏览器根据form的参数或者a的参数 作为访问的地址
2 与服务器创建TCP连接
3 服务器组建HTML文本 然后关闭连接
4 浏览器将当前显示的页面摧毁 并按照新的HTML文本呈现新的页面给用户
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连接
1 客户端与服务器建立TCP连接
2 服务器根据客户端提交的报文处理并生成HTML文本
3 将HTML封闭成为HTTP协议报文并返回给客户端
4 关闭链接
看到这个处理过程
不难联想到 如果把第4步 关闭连接给省掉  相当于有一个长连接一直被维持住
通过对服务端的一些操作
可以直接将数据从这个TCP连接发送客户端了
这种技术大大提高服务器推送的实时性 还可以减去服务端不停地建立 施放连接所形成的开销
目前市面上有不少基于AJAX实现的Comet机制
但主要有两种方式
1 建立连接后依然用"询问""应答"模式 工作方式没变 因为减去每次建立与施放连接性能提升了 且服务器对TCP连接有上下文定义 不像AJAX完全是无状态
2 对Stream写入实现服务器将数据主动发送到客户端 因为是TCP连接 所以通过对服务器的编程 可主动把数据从服务端发送给客户端 模式上真正建立了推送

Server-Sent
Server-Sent是HTML5提出一个标准
延用了Comet的思路 并对其进行了一些规范
使得Comet这项技术由原来的分支衍生技术转成了正统的官方标准
原理与Comet相同 由客户端发起与服务器之间创建TCP连接 然后并维持这个连接 至到客户端或服务器中的做任何一放断开 ServerSent使用的是"问"+"答"的机制
连接创建后浏览器会周期性地发送消息至服务器询问 是否有自己的消息
这项标准不仅要求支持的浏览器能原生态创建与服务器长连接 更要求对JavaScript脚本统一性 使兼程该功能的浏览器可用同一套代码完成Server-Sent编码工作
创建代码//定义一个ServerSent对象
var 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 = "text/event-stream";
context.Response.Expires = -1;
context.Response.Write("event: MyEvent\r\n");       //事件类型 使用\r\n结尾
context.Response.Write("data: HelloWorld!\r\n");    //事件数据 换行时使用\r\n 并在新行再加上data:
context.Response.Write("data: I'm server!\n\n");    //事件数据结束 使用\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连接
var ws = new WebSocket("ws://192.168.0.105:10080"); //连接服务器        
ws.onopen = function (event) { alert("已经与服务器建立了连接\r\n当前连接状态:" + this.readyState); };
ws.onmessage = function (event) { alert("接收到服务器发送的数据:\r\n" + event.data); };
ws.onclose = function (event) { alert("已经与服务器断开连接\r\n当前连接状态:" + this.readyState); };
ws.onerror = function (event) { alert("WebSocket异常!"); };
还可以通过send的方式发送消息
ws.send("Hello World");
WebSocket具有较为复杂的协议 需要在服务端做额外编程才能进行数据通讯 有关协议的详细内容 我会在以后的文章中进行解释
WebSocket + MessageQueue
MessageQueue 简称MQ 也就是消息列队 是一种常常用于Tcp服务端的技术 通过生产和访问各种消息类型 MQ服务器会将生产者所生成的消息发给感兴趣的客户端 市面上有很多的MQ框架 比如:ActiveMQ
ActiveMQ已经支持了WebSocket协议 也就意味着 WebSocket已经可以作为一个生产者或一个消费者 与MQ服务器连接
开发者可以通过MQTT的JS脚本 连接上MQ服务器 同时将Web服务器也连上MQ服务器 从此可以告别了Http通讯协议 完完全全使用Socket通讯来完成数据的交换
总得来说 在HTML5规范下 最推荐使用 ServerSent 和 WebSocket 的方式进行服务器消息的推送
对比这两种方式
ServerSent的方式 可以使服务端的开发依然依用以前的方式 但是其工作方式与Comet类似
而WebSocket的方式 则对服务端的开发有着较高的要求 但其工作方式是完全的推送
偏向WebSocket + MQ 工作方式 但是对于老项目的翻新 还是用 SeverSent 比较好