前言:在各種BS架構(gòu)的應(yīng)用程序中冀膝,往往都希望服務(wù)端能夠主動地向客戶端推送各種消息唁奢,以達到類似于郵件、消息窝剖、待辦事項等通知驮瞧。而BS架構(gòu)本身存在的問題就是,服務(wù)器一直采用的是一問一答的機制枯芬。這就意味著如果客戶端不主動地向服務(wù)器發(fā)送消息论笔,服務(wù)器就無法得知如何給客戶端推送消息。
隨著HTML千所、瀏覽器等各項技術(shù)狂魔、標(biāo)準(zhǔn)的發(fā)展,依次生成了不同的手段與方法能夠?qū)崿F(xiàn)服務(wù)端主動推送消息淫痰,它們分別是:AJAX最楷,Comet,ServerSent 以及 WebSocket待错。
本篇文章將對上述提及到的各種技術(shù)手段進行直白化的解釋籽孙。
AJAX
- 正常的一個頁面在瀏覽器中是這樣工作的:
1、用戶向給予瀏覽器一個需要訪問的地址火俄。
2犯建、瀏覽器根據(jù)這個地址訪問服務(wù)器,并與服務(wù)器之間創(chuàng)建一個TCP連接(HTTP請求)瓜客。
3适瓦、服務(wù)器根據(jù)這個地址和一些其它數(shù)據(jù),組建一段HTML文本谱仪,將寫入TCP連接玻熙,然后關(guān)閉連接。
4疯攒、瀏覽器得到了來自服務(wù)器的HTML文本嗦随,解析并呈現(xiàn)了瀏覽器上給用戶瀏覽。 - 此時敬尺,用戶點擊了網(wǎng)站上任何一個<a>或觸發(fā)任何一個<form>提交時:
1枚尼、瀏覽器根據(jù)form的參數(shù)或者a的參數(shù)肌毅,作為訪問的地址。
2姑原、與服務(wù)器創(chuàng)建TCP連接悬而。
3、服務(wù)器組建HTML文本锭汛,然后關(guān)閉連接笨奠。
4、瀏覽器將當(dāng)前顯示的頁面摧毀唤殴,并按照新的HTML文本呈現(xiàn)一個新的頁面給用戶般婆。
我們不難發(fā)現(xiàn)的是整個過程中間,一旦建立了一個連接朵逝,頁面就無法再維護住了蔚袍。整個過程看上去有點強買強賣,也許我只要一杯新的可樂配名,但你非要給我一整個套餐組合啤咽。
此時我們可以了解一下 XmlHttpRequest 組件,這個組件提供我們手動創(chuàng)建一個HTTP請求渠脉,發(fā)送我們想要的數(shù)據(jù)宇整,服務(wù)器也可以只返回我們想要的結(jié)果,最大的好處是芋膘,當(dāng)我們收到服務(wù)器的響應(yīng)時鳞青,原來的頁面沒有被摧毀。這就好比为朋,我喊一句"我的咖啡喝完了臂拓,我要續(xù)杯",然后服務(wù)員就拿了一杯咖啡過來习寸,而不是會把我沒吃完的套餐全部倒掉胶惰。
當(dāng)我們利用 AJAX 實現(xiàn)服務(wù)器推送時,其實質(zhì)是客戶端不停地向服務(wù)器詢問 "有沒有給我的消息呀融涣?" 童番,然后服務(wù)器回答 "有" 或 "沒有" 來達到的實現(xiàn)效果精钮。它的實現(xiàn)方法也很簡單威鹿,利用jQuery框架封裝好的AJAX調(diào)用也很方便:
function getMessage(fn) {
$.ajax({
url: "Handler.ashx", //一個能夠提供消息的頁面
dataType: "text", //響應(yīng)類型,可以是JSON轨香,XML等其它類型
type: "get", //HTTP請求類型忽你,還可以是post
success: function (d, s) {
fn(d); //得到了正常的響應(yīng)時,利用回調(diào)函數(shù)通知外部
},
complete: function (x, s) {
setTimeout(function () {
getMessage(fn);
}, 5000); //無論響應(yīng)成功或失敗臂容,在若干秒后再詢問一次服務(wù)器
}
});
}
通過上面的代碼科雳,可以每隔5秒詢問一次服務(wù)器是否有需要處理的消息根蟹,通過這種方式可以達到推送的效果,但是會存在一個問題:
- 間隔時間越快糟秘,推送的及時性越好简逮,服務(wù)器的消費越大;
- 間隔時間越慢尿赚,推送的及時性越低散庶,服務(wù)器的消費越小。
而且嚴(yán)格地來說凌净,這種實際方式悲龟,并不是真正意義上的服務(wù)器主動推送消息,但由于早期技術(shù)手段缺乏冰寻,所以AJAX輪循成為了一種很普遍的手段须教。
Comet
- 我們知道HTTP請求其實是基于TCP連接實現(xiàn)的,再看看之前說的HTTP請求處理過程:
1斩芭、客戶端與服務(wù)器建立TCP連接
2轻腺、服務(wù)器根據(jù)客戶端提交的報文處理并生成HTML文本
3、將HTML封閉成為HTTP協(xié)議報文并返回給客戶端
4划乖、關(guān)閉鏈接约计。
5、看到這個處理過程迁筛,我們不難聯(lián)想到煤蚌,如果把第4步——關(guān)閉連接給省掉,那不就相當(dāng)于有一個長連接一直被維持住了么细卧。通過對服務(wù)端的一些操作尉桩,我們可以直接將數(shù)據(jù)從這個TCP連接發(fā)送客戶端了。
通過這種技術(shù)贪庙,我們可以大大提高服務(wù)器推送的實時性蜘犁,還可以減去服務(wù)端不停地建立、施放連接所形成的開銷止邮。
- 目前市面上有不少基于AJAX實現(xiàn)的Comet機制这橙,但主要有兩種方式:
1、建立連接后依然使用"詢問"+"應(yīng)答"的模式导披。雖然工作方式?jīng)]變屈扎,但是因為減去了每次建立與施放連接的工作,所以性能上提升了很多撩匕。而且服務(wù)器對TCP連接可以有上下文的定義鹰晨,而不像以前的AJAX完全是無狀態(tài)的。
2、通過對Stream的寫入實現(xiàn)服務(wù)器將數(shù)據(jù)主動發(fā)送到客戶端模蜡。因為是TCP連接漠趁,所以通過對服務(wù)器的編程,我們可以主動的把數(shù)據(jù)從服務(wù)端發(fā)送給客戶端忍疾,從模式上真正建立起了推送的概念闯传。
Server-Sent
Server-Sent是HTML5提出一個標(biāo)準(zhǔn),它延用了Comet的思路卤妒,并對其進行了一些規(guī)范丸边。使得Comet這項技術(shù)由原來的分支衍生技術(shù)轉(zhuǎn)成了正統(tǒng)的官方標(biāo)準(zhǔn)。
它的原理與Comet相同荚孵,由客戶端發(fā)起與服務(wù)器之間創(chuàng)建TCP連接妹窖,然后并維持這個連接,至到客戶端或服務(wù)器中的做任何一放斷開收叶,ServerSent使用的是"問"+"答"的機制骄呼,連接創(chuàng)建后瀏覽器會周期性地發(fā)送消息至服務(wù)器詢問,是否有自己的消息判没。
這項標(biāo)準(zhǔn)不僅要求了支持的瀏覽器能夠原生態(tài)的創(chuàng)建與服務(wù)器的長連接蜓萄,更要求了對JavaScript腳本的統(tǒng)一性,使得兼程該功能的瀏覽器可以使用同一套代碼完成Server-Sent的編碼工作澄峰。
創(chuàng)建代碼非常簡單:
//定義一個ServerSent對象
var s = new EventSource("Handler.ashx");
//當(dāng)收到一個非自定義事件時的回調(diào)函數(shù)
s.onmessage = function (e) {
alert(e.data);
};
//當(dāng)收到一個被服務(wù)器命名為MyEvent事件消息時的回調(diào)函數(shù)
s.addEventListener("MyEvent", function (e) {
alert(e.data);
});
而服務(wù)器的代碼也很簡單:
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結(jié)尾
context.Response.Write("data: HelloWorld!\r\n"); //事件數(shù)據(jù),換行時使用\r\n俏竞,并在新行再加上data:
context.Response.Write("data: I'm server!\n\n"); //事件數(shù)據(jù)結(jié)束绸硕,使用\n\n
context.Response.Flush(); //這里不能用End,否則是關(guān)閉連接的
}
public bool IsReusable
{
get
{
return true;
}
}
}
兩小段代碼魂毁,就已經(jīng)具備了服務(wù)器消息推送了玻佩。
總得來說 SeverSent 就是 HTML5 規(guī)范下的 Comet ,具有更好的統(tǒng)一性席楚,而且簡單好用咬崔。
WebSocket
看名字就知道了,這是一個可以用在瀏覽器上的Socket連接烦秩。
這也是一個HTML5標(biāo)準(zhǔn)中的一項內(nèi)容垮斯,他要求瀏覽器可以通過JavaScript腳本手動創(chuàng)建一個TCP連接與服務(wù)端進行通訊。
WebSocket不包含太多的額外功能只祠,僅僅就是TCP連接的幾項基本功能:建立兜蠕,臨時以及發(fā)送。
另外WebSocket使用了ws和wss協(xié)議铆农,需要服務(wù)器有與之握手的算法才能將連接打開牺氨。
所以WebSocket相對于之前幾種手段來說狡耻,其編碼量是最大的墩剖,但由于沒有其它的約束猴凹,因此它也可以自由地實現(xiàn)所有可能的功能。
即可以滿足"問"+"答"的響應(yīng)機制岭皂,也可以實現(xiàn)主動推送的功能郊霎。
與ServerSent相同,HTML5也對WebSocket調(diào)用的JavaScript進行規(guī)范爷绘,我們可以弄過很簡單的一代碼構(gòu)建一個WebSocket連接书劝。
var ws = new WebSocket("ws://192.168.0.105:10080"); //連接服務(wù)器
ws.onopen = function (event) { alert("已經(jīng)與服務(wù)器建立了連接\r\n當(dāng)前連接狀態(tài):" + this.readyState); };
ws.onmessage = function (event) { alert("接收到服務(wù)器發(fā)送的數(shù)據(jù):\r\n" + event.data); };
ws.onclose = function (event) { alert("已經(jīng)與服務(wù)器斷開連接\r\n當(dāng)前連接狀態(tài):" + this.readyState); };
ws.onerror = function (event) { alert("WebSocket異常!"); };
還可以通過send的方式發(fā)送消息土至。
ws.send("Hello World");
WebSocket具有較為復(fù)雜的協(xié)議购对,需要在服務(wù)端做額外編程才能進行數(shù)據(jù)通訊。有關(guān)協(xié)議的詳細(xì)內(nèi)容陶因,我會在以后的文章中進行解釋骡苞。
WebSocket + MessageQueue
MessageQueue,簡稱MQ楷扬,也就是消息列隊解幽。是一種常常用于Tcp服務(wù)端的技術(shù)。通過生產(chǎn)和訪問各種消息類型烘苹,MQ服務(wù)器會將生產(chǎn)者所生成的消息發(fā)給感興趣的客戶端躲株。市面上有很多的MQ框架,比如:ActiveMQ镣衡。
ActiveMQ已經(jīng)支持了WebSocket協(xié)議霜定,也就意味著,WebSocket已經(jīng)可以作為一個生產(chǎn)者或一個消費者廊鸥,與MQ服務(wù)器連接然爆。
開發(fā)者可以通過MQTT的JS腳本,連接上MQ服務(wù)器黍图,同時將Web服務(wù)器也連上MQ服務(wù)器曾雕,從此可以告別了Http通訊協(xié)議,完完全全使用Socket通訊來完成數(shù)據(jù)的交換助被。
總得來說剖张,在HTML5規(guī)范下,最推薦使用 ServerSent 和 WebSocket 的方式進行服務(wù)器消息的推送揩环。
- 對比這兩種方式搔弄。
ServerSent的方式,可以使服務(wù)端的開發(fā)依然依用以前的方式丰滑,但是其工作方式與Comet類似顾犹。而WebSocket的方式倒庵,則對服務(wù)端的開發(fā)有著較高的要求,但其工作方式是完全的推送炫刷。
我本人其實挺偏向WebSocket + MQ的工作方式擎宝,但是對于老項目的翻新,還是用 SeverSent 比較好浑玛。