摘要在各種BS架構(gòu)的應(yīng)用程序中,往往都希望服務(wù)端能夠主動(dòng)地向客戶(hù)端推送各種消息,以達(dá)到類(lèi)似于郵件、消息、待辦事項(xiàng)等通知。 往BS架構(gòu)本身存在的問(wèn)題就是,服務(wù)器一直采用的是一問(wèn)一答的機(jī)制。這就意味著如果客戶(hù)端不主動(dòng)地向服務(wù)器發(fā)送消息,服務(wù)器就無(wú)法得知如何給客戶(hù)端推送消息。 隨著HTML、瀏覽器等各項(xiàng)技術(shù)、標(biāo)準(zhǔn)的發(fā)展,依次生成了不同的手段與方法能夠?qū)崿F(xiàn)服務(wù)端主動(dòng)推送消息,它們分別是:AJAX,Comet,ServerSent以及WebSocket。 本篇文章將對(duì)上述提及到的各種技術(shù)手段進(jìn)行直白化的解釋。 AJAX正常的一個(gè)頁(yè)面在瀏覽器中是這樣工作的:
此時(shí),用戶(hù)點(diǎn)擊了網(wǎng)站上任何一個(gè)<a>或觸發(fā)任何一個(gè)<form>提交時(shí):
我們不難發(fā)現(xiàn)的是整個(gè)過(guò)程中間,一旦建立了一個(gè)連接,頁(yè)面就無(wú)法再維護(hù)住了。整個(gè)過(guò)程看上去有點(diǎn)強(qiáng)買(mǎi)強(qiáng)賣(mài),也許我只要一杯新的可樂(lè),但你非要給我一整個(gè)套餐組合。 此時(shí)我們可以了解一下XmlHttpRequest組件,這個(gè)組件提供我們手動(dòng)創(chuàng)建一個(gè)HTTP請(qǐng)求,發(fā)送我們想要的數(shù)據(jù),服務(wù)器也可以只返回我們想要的結(jié)果,最大的好處是,當(dāng)我們收到服務(wù)器的響應(yīng)時(shí),原來(lái)的頁(yè)面沒(méi)有被摧毀。這就好比,我喊一句"我的咖啡喝完了,我要續(xù)杯",然后服務(wù)員就拿了一杯咖啡過(guò)來(lái),而不是會(huì)把我沒(méi)吃完的套餐全部倒掉。 當(dāng)我們利用AJAX實(shí)現(xiàn)服務(wù)器推送時(shí),其實(shí)質(zhì)是客戶(hù)端不停地向服務(wù)器詢(xún)問(wèn)"有沒(méi)有給我的消息呀?",然后服務(wù)器回答"有"或"沒(méi)有"來(lái)達(dá)到的實(shí)現(xiàn)效果。它的實(shí)現(xiàn)方法也很簡(jiǎn)單,利用jQuery框架封裝好的AJAX調(diào)用也很方便: function getMessage(fn) { $.ajax({ url: "Handler.ashx", //一個(gè)能夠提供消息的頁(yè)面 dataType: "text", //響應(yīng)類(lèi)型,可以是JSON,XML等其它類(lèi)型 type: "get", //HTTP請(qǐng)求類(lèi)型,還可以是post success: function (d, s) { fn(d); //得到了正常的響應(yīng)時(shí),利用回調(diào)函數(shù)通知外部 }, complete: function (x, s) { setTimeout(function () { getMessage(fn); }, 5000); //無(wú)論響應(yīng)成功或失敗,在若干秒后再詢(xún)問(wèn)一次服務(wù)器 } }); } 通過(guò)上面的代碼,可以每隔5秒詢(xún)問(wèn)一次服務(wù)器是否有需要處理的消息,通過(guò)這種方式可以達(dá)到推送的效果,但是會(huì)存在一個(gè)問(wèn)題:
而且嚴(yán)格地來(lái)說(shuō),這種實(shí)際方式,并不是真正意義上的服務(wù)器主動(dòng)推送消息,但由于早期技術(shù)手段缺乏,所以AJAX輪循成為了一種很普遍的手段。
Comet我們知道HTTP請(qǐng)求其實(shí)是基于TCP連接實(shí)現(xiàn)的,再看看之前說(shuō)的HTTP請(qǐng)求處理過(guò)程:
看到這個(gè)處理過(guò)程,我們不難聯(lián)想到,如果把第4步——關(guān)閉連接給省掉,那不就相當(dāng)于有一個(gè)長(zhǎng)連接一直被維持住了么。通過(guò)對(duì)服務(wù)端的一些操作,我們可以直接將數(shù)據(jù)從這個(gè)TCP連接發(fā)送客戶(hù)端了。 通過(guò)這種技術(shù),我們可以大大提高服務(wù)器推送的實(shí)時(shí)性,還可以減去服務(wù)端不停地建立、施放連接所形成的開(kāi)銷(xiāo)。 目前市面上有不少基于AJAX實(shí)現(xiàn)的Comet機(jī)制,但主要有兩種方式:
Server-SentServer-Sent是HTML5提出一個(gè)標(biāo)準(zhǔn),它延用了Comet的思路,并對(duì)其進(jìn)行了一些規(guī)范。使得Comet這項(xiàng)技術(shù)由原來(lái)的分支衍生技術(shù)轉(zhuǎn)成了正統(tǒng)的官方標(biāo)準(zhǔn)。 它的原理與Comet相同,由客戶(hù)端發(fā)起與服務(wù)器之間創(chuàng)建TCP連接,然后并維持這個(gè)連接,至到客戶(hù)端或服務(wù)器中的做任何一放斷開(kāi),ServerSent使用的是"問(wèn)"+"答"的機(jī)制,連接創(chuàng)建后瀏覽器會(huì)周期性地發(fā)送消息至服務(wù)器詢(xún)問(wèn),是否有自己的消息。 這項(xiàng)標(biāo)準(zhǔn)不僅要求了支持的瀏覽器能夠原生態(tài)的創(chuàng)建與服務(wù)器的長(zhǎng)連接,更要求了對(duì)JavaScript腳本的統(tǒng)一性,使得兼程該功能的瀏覽器可以使用同一套代碼完成Server-Sent的編碼工作。 創(chuàng)建代碼非常簡(jiǎn)單: //定義一個(gè)ServerSent對(duì)象 var s = new EventSource("Handler.ashx"); //當(dāng)收到一個(gè)非自定義事件時(shí)的回調(diào)函數(shù) s.onmessage = function (e) { alert(e.data); }; //當(dāng)收到一個(gè)被服務(wù)器命名為MyEvent事件消息時(shí)的回調(diào)函數(shù) s.addEventListener("MyEvent", function (e) { alert(e.data); }); 而服務(wù)器的代碼也很簡(jiǎn)單: 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"); //事件類(lèi)型,使用\r\n結(jié)尾 context.Response.Write("data: HelloWorld!\r\n"); //事件數(shù)據(jù),換行時(shí)使用\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ù)器消息推送了。 總得來(lái)說(shuō)SeverSent就是HTML5規(guī)范下的Comet,具有更好的統(tǒng)一性,而且簡(jiǎn)單好用。
WebSocket看名字就知道了,這是一個(gè)可以用在瀏覽器上的Socket連接。 這也是一個(gè)HTML5標(biāo)準(zhǔn)中的一項(xiàng)內(nèi)容,他要求瀏覽器可以通過(guò)JavaScript腳本手動(dòng)創(chuàng)建一個(gè)TCP連接與服務(wù)端進(jìn)行通訊。 WebSocket不包含太多的額外功能,僅僅就是TCP連接的幾項(xiàng)基本功能:建立,臨時(shí)以及發(fā)送。 另外WebSocket使用了ws和wss協(xié)議,需要服務(wù)器有與之握手的算法才能將連接打開(kāi)。 所以WebSocket相對(duì)于之前幾種手段來(lái)說(shuō),其編碼量是最大的,但由于沒(méi)有其它的約束,因此它也可以自由地實(shí)現(xiàn)所有可能的功能。 即可以滿(mǎn)足"問(wèn)"+"答"的響應(yīng)機(jī)制,也可以實(shí)現(xiàn)主動(dòng)推送的功能。 與ServerSent相同,HTML5也對(duì)WebSocket調(diào)用的JavaScript進(jìn)行規(guī)范,我們可以弄過(guò)很簡(jiǎn)單的一代碼構(gòu)建一個(gè)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ù)器斷開(kāi)連接\r\n當(dāng)前連接狀態(tài):" + this.readyState); }; ws.onerror = function (event) { alert("WebSocket異常!"); }; 還可以通過(guò)send的方式發(fā)送消息 ws.send("Hello World"); WebSocket具有較為復(fù)雜的協(xié)議,需要在服務(wù)端做額外編程才能進(jìn)行數(shù)據(jù)通訊。有關(guān)協(xié)議的詳細(xì)內(nèi)容,我會(huì)在以后的文章中進(jìn)行解釋。
WebSocket + MessageQueueMessageQueue,簡(jiǎn)稱(chēng)MQ,也就是消息列隊(duì)。是一種常常用于Tcp服務(wù)端的技術(shù)。通過(guò)生產(chǎn)和訪(fǎng)問(wèn)各種消息類(lèi)型,MQ服務(wù)器會(huì)將生產(chǎn)者所生成的消息發(fā)給感興趣的客戶(hù)端。市面上有很多的MQ框架,比如:ActiveMQ。 ActiveMQ已經(jīng)支持了WebSocket協(xié)議,也就意味著,WebSocket已經(jīng)可以作為一個(gè)生產(chǎn)者或一個(gè)消費(fèi)者,與MQ服務(wù)器連接。 開(kāi)發(fā)者可以通過(guò)MQTT的JS腳本,連接上MQ服務(wù)器,同時(shí)將Web服務(wù)器也連上MQ服務(wù)器,從此可以告別了Http通訊協(xié)議,完完全全使用Socket通訊來(lái)完成數(shù)據(jù)的交換。
總結(jié):總得來(lái)說(shuō),在HTML5規(guī)范下,最推薦使用ServerSent和WebSocket的方式進(jìn)行服務(wù)器消息的推送。 對(duì)比這兩種方式。 ServerSent的方式,可以使服務(wù)端的開(kāi)發(fā)依然依用以前的方式,但是其工作方式與Comet類(lèi)似。 而WebSocket的方式,則對(duì)服務(wù)端的開(kāi)發(fā)有著較高的要求,但其工作方式是完全的推送。 我本人其實(shí)挺偏向WebSocket + MQ的工作方式,但是對(duì)于老項(xiàng)目的翻新,還是用SeverSent比較好
結(jié)尾本文為作者原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處:http://www.cnblogs.com/ShimizuShiori/p/5464063.html 文章中的相關(guān)代碼可以在 http://j./Develop/Explorer.aspx 中的ServerSent目錄中查看
|
|