WEB服務(wù)器推送消息的各種解決辦法

前言:在各種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 比較好浑玛。


原文鏈接:https://www.cnblogs.com/Herzog3/p/5939144.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绍申,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子顾彰,更是在濱河造成了極大的恐慌极阅,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涨享,死亡現(xiàn)場離奇詭異筋搏,居然都是意外死亡,警方通過查閱死者的電腦和手機厕隧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門奔脐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人栏账,你說我怎么就攤上這事帖族。” “怎么了挡爵?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵竖般,是天一觀的道長。 經(jīng)常有香客問我茶鹃,道長涣雕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任闭翩,我火速辦了婚禮挣郭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疗韵。我一直安慰自己兑障,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布蕉汪。 她就那樣靜靜地躺著流译,像睡著了一般。 火紅的嫁衣襯著肌膚如雪者疤。 梳的紋絲不亂的頭發(fā)上福澡,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音驹马,去河邊找鬼革砸。 笑死除秀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的算利。 我是一名探鬼主播册踩,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼笔时!你這毒婦竟也來了棍好?” 一聲冷哼從身側(cè)響起仗岸,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤允耿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扒怖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體较锡,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年盗痒,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚂蕴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡俯邓,死狀恐怖骡楼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情稽鞭,我是刑警寧澤鸟整,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站朦蕴,受9級特大地震影響篮条,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吩抓,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一涉茧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疹娶,春花似錦伴栓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沛膳,卻和暖如春扔枫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锹安。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工短荐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留倚舀,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓忍宋,卻偏偏與公主長得像痕貌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子糠排,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容