<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實(shí)時(shí)消息推送整理

          共 7800字,需瀏覽 16分鐘

           ·

          2020-10-24 05:32

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          ? 作者?|??糊糊糊糊糊了

          來源 |? urlify.cn/NviAbe

          66套java從入門到精通實(shí)戰(zhàn)課程分享

          分不清輪詢、長輪詢?不知道什么時(shí)候該用websocket還是SSE,看這篇就夠了。

          所謂的“實(shí)時(shí)推送”,從表面意思上來看是,客戶端訂閱的內(nèi)容在發(fā)生改變時(shí),服務(wù)器能夠?qū)崟r(shí)地通知客戶端,進(jìn)而客戶端進(jìn)行相應(yīng)地反應(yīng)。客戶端不需要主觀地發(fā)送請(qǐng)求去獲取自己關(guān)心的內(nèi)容,而是由服務(wù)器端進(jìn)行“推送”。

          注意上面的推送二字打了引號(hào),這就意味著在現(xiàn)有的幾種實(shí)現(xiàn)方式中,并不是服務(wù)器端主動(dòng)地推送,而是通過一定的手段營造了一種實(shí)時(shí)的假象。就目前現(xiàn)有的幾種技術(shù)而言,主要有以下幾類:

          • 客戶端輪詢:傳統(tǒng)意義上的輪詢(Short Polling)

          • 服務(wù)器端輪詢:長輪詢(Long Polling)

          • 全雙工通信:Websocket

          • 單向服務(wù)器推送:Server-Sent Events(SSE)

          文中會(huì)以一個(gè)簡易聊天室的例子來分別通過上述的四種方式實(shí)現(xiàn),代碼地址mini-chatroom(存在些許bug,主要是為了做演示用)

          輪詢(Short Polling)

          輪詢的實(shí)現(xiàn)原理:客戶端向服務(wù)器端發(fā)送一個(gè)請(qǐng)求,服務(wù)器返回?cái)?shù)據(jù),然后客戶端根據(jù)服務(wù)器端返回的數(shù)據(jù)進(jìn)行處理;然后客戶端繼續(xù)向服務(wù)器端發(fā)送請(qǐng)求,繼續(xù)重復(fù)以上的步驟,如果不想給服務(wù)器端太大的壓力,一般情況下會(huì)設(shè)置一個(gè)請(qǐng)求的時(shí)間間隔。

          使用輪詢明顯的優(yōu)點(diǎn)是基礎(chǔ)不需要額外的開發(fā)成本,請(qǐng)求數(shù)據(jù),解析數(shù)據(jù),作出響應(yīng),僅此而已,然后不斷重復(fù)。缺點(diǎn)也顯而易見:

          • 不斷的發(fā)送和關(guān)閉請(qǐng)求,對(duì)服務(wù)器的壓力會(huì)比較大,因?yàn)楸旧黹_啟Http連接就是一件比較耗資源的事情

          • 輪詢的時(shí)間間隔不好控制。如果要求的實(shí)時(shí)性比較高,顯然使用短輪詢會(huì)有明顯的短板,如果設(shè)置interval的間隔過長,會(huì)導(dǎo)致消息延遲,而如果太短,會(huì)對(duì)服務(wù)器產(chǎn)生壓力

          代碼實(shí)現(xiàn)

          var?ShortPollingNotification?=?{
          ??datasInterval:?null,
          ??subscribe:?function()?{
          ????this.datasInterval?=?setInterval(function()?{
          ??????Request.getDatas().then(function(res)?{
          ????????window.ChatroomDOM.renderData(res);
          ??????});
          ????},?TIMEOUT);
          ????return?this.unsubscribe;
          ??},
          ??unsubscribe:?function()?{
          ????this.datasInterval?&&?clearInterval(this.datasInterval);
          ??}
          }

          下面是對(duì)應(yīng)的請(qǐng)求,注意左下角的請(qǐng)求數(shù)量一直在變化

          在上圖中,每隔1s就會(huì)發(fā)送一個(gè)請(qǐng)求,看起來效果還不錯(cuò),但是如果將timeout的值設(shè)置成5s,效果將大打折扣,如圖:

          長輪詢(Long Polling)

          長輪詢的基本原理:客戶端發(fā)送一個(gè)請(qǐng)求,服務(wù)器會(huì)hold住這個(gè)請(qǐng)求,直到監(jiān)聽的內(nèi)容有改變,才會(huì)返回?cái)?shù)據(jù),斷開連接,客戶端繼續(xù)發(fā)送請(qǐng)求,重復(fù)以上步驟。或者在一定的時(shí)間內(nèi),請(qǐng)求還得不到返回,就會(huì)因?yàn)槌瑫r(shí)自動(dòng)斷開連接。

          長輪詢是基于輪詢上的改進(jìn)版本,主要是減少了客戶端發(fā)起Http連接的開銷,改成了在服務(wù)器端主動(dòng)地去判斷所關(guān)心的內(nèi)容是否變化,所以其實(shí)輪詢的本質(zhì)并沒有多大變化,變化的點(diǎn)在于:

          • 對(duì)于內(nèi)容變化的輪詢由客戶端改成了服務(wù)器端(客戶端會(huì)在連接中斷之后,會(huì)再次發(fā)送請(qǐng)求,對(duì)比短輪詢來說,大大減少了發(fā)起連接的次數(shù))

          • 客戶端只會(huì)在數(shù)據(jù)改變時(shí)去作相應(yīng)的改變,對(duì)比短輪詢來說,并不是全盤接收

          代碼實(shí)現(xiàn)

          //?客戶端
          var?LongPollingNotification?=?{
          ????//?....
          ????subscribe:?function()?{
          ??????var?that?=?this;

          ??????//?設(shè)置超時(shí)時(shí)間
          ??????Request.getV2Datas(this.getKey(),{?timeout:?10000?}).then(function(res)?{
          ????????var?data?=?res.data;
          ????????window.ChatroomDOM.renderData(res);
          ????????//?成功獲取數(shù)據(jù)后會(huì)再次發(fā)送請(qǐng)求
          ????????that.subscribe();
          ??????}).catch(function?(error)?{
          ????????//?timeout?之后也會(huì)再次發(fā)送請(qǐng)求
          ????????that.subscribe();
          ??????});
          ??????return?this.unsubscribe;
          ????}

          ????//?....
          }

          筆者采用的是express,默認(rèn)不支持hold住請(qǐng)求,因此用了一個(gè)express-longpoll的庫來實(shí)現(xiàn)。

          下面是一個(gè)原生不用庫的實(shí)現(xiàn)(這里只是介紹原理),整體的思路是:如果服務(wù)器端支持hold住請(qǐng)求的話,那么在一定的時(shí)間內(nèi)會(huì)自輪詢,然后期間通過比較key值,判斷是否返回新數(shù)據(jù)

          • 客戶端第一次會(huì)帶一個(gè)空的key值,這次會(huì)立即返回,獲取新內(nèi)容,服務(wù)器端將計(jì)算出的contentKey返回給客戶端

          • 然后客戶端發(fā)送第二次請(qǐng)求,帶上第一次返回的contentKey作為key值,然后進(jìn)行下一輪的比較

          • 如果兩次的key值相同,就會(huì)hold請(qǐng)求,進(jìn)行內(nèi)部輪詢,如果期間有新內(nèi)容或者客戶端timeout,就會(huì)斷開連接

          • 重復(fù)以上步驟

          //?服務(wù)器端

          router.get('/v2/datas',?function(req,?res)?{
          ??const?key?=?_.get(req.query,?'key',?'');
          ??let?contentKey?=?chatRoom.getContentKey();

          ??while?(key?===?contentKey)?{
          ????sleep.sleep(5);
          ????contentKey?=?chatRoom.getContentKey();
          ??}

          ??const?connectors?=?chatRoom.getConnectors();
          ??const?messages?=?chatRoom.getMessages();
          ??res.json({
          ????code:?200,
          ????data:?{?connectors:?connectors,?messages:?messages,?key:?contentKey?},
          ??});
          });

          以下是用?express-longpoll?的實(shí)現(xiàn)片段

          //?mini-chatroom/public/javascripts/server/longPolling.js

          function?pushDataToClient(key,?longpoll)?{
          ??var?contentKey?=?chatRoom.getContentKey();

          ??if?(key?!==?contentKey)?{
          ????var?connectors?=?chatRoom.getConnectors();
          ????var?messages?=?chatRoom.getMessages();

          ????longpoll.publish(
          ??????'/v2/datas',
          ??????{
          ????????code:?200,
          ????????data:?{connectors:?connectors,?messages:?messages,?key:?contentKey},
          ??????}
          ????);
          ??}
          }

          longpoll.create("/v2/datas",?function(req,?res,?next)?{
          ??key?=?_.get(req.query,?'key',?'');
          ??pushDataToClient(key,?longpoll);
          ??next();
          });

          intervalId?=?setInterval(function()?{
          ??pushDataToClient(key,?longpoll);
          },?LONG_POLLING_TIMEOUT);

          為了方便演示,我將客戶端發(fā)起請(qǐng)求的timeout改成了4s,注意觀察下面的截圖:

          可以看到,斷開連接的兩種方式,要么是超時(shí),要么是請(qǐng)求有數(shù)據(jù)返回。

          基于iframe的長輪詢模式

          這種模式的具體的原理為:

          • 在頁面中嵌入一個(gè)iframe,地址指向輪詢的服務(wù)器地址,然后在父頁面中放置一個(gè)執(zhí)行函數(shù),比如execute(data)

          • 當(dāng)服務(wù)器有內(nèi)容改變時(shí),會(huì)向iframe發(fā)送一個(gè)腳本

          • 通過發(fā)送的腳本,主動(dòng)執(zhí)行父頁面中的方法,達(dá)到推送的效果

          具體可以參看這里

          Websocket

          The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code.

          The protocol consists of an opening handshake followed by basic message framing, layered over TCP.

          The goal of this technology is to provide a mechanism for browser-based applications that need two-way communication with servers that does not rely on opening multiple HTTP connections (e.g., using XMLHttpRequest or iframe and long polling).

          The WebSocket Protocol attempts to address the goals of existing bidirectional HTTP technologies in the context of the existing HTTP infrastructure; as such, it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries, even if this implies some complexity specific to the current environment.

          特征

          • websocket是雙向通信的,設(shè)計(jì)的目的主要是為了減少傳統(tǒng)輪詢時(shí)http連接數(shù)量的開銷

          • 建立在TCP協(xié)議之上,握手階段采用 HTTP 協(xié)議,因此握手時(shí)不容易屏蔽,能通過各種 HTTP 代理服務(wù)器

          • 與HTTP兼容性良好,同樣可以使用80和443端口

          • 沒有同源限制,客戶端可以與任意服務(wù)器通信

          • 可以發(fā)送文本,也可以發(fā)送二進(jìn)制數(shù)據(jù)。

          • 協(xié)議標(biāo)識(shí)符是ws(如果加密,則為wss),服務(wù)器網(wǎng)址就是 URL

          關(guān)于Websocket API方面的知識(shí),這里不再作講解,可以自己查閱Websocket API MDN

          兼容性

          websocket兼容性良好,基本支持所有現(xiàn)代瀏覽器

          代碼實(shí)現(xiàn)

          筆者這里采用的是socket.io,是基于websocket的封裝,提供了客戶端以及服務(wù)器端的支持

          //?客戶端
          var?WebsocketNotification?=?{
          ??//?...
          ??subscribe:?function(args)?{
          ????var?connector?=?args[1];
          ????this.socket?=?io();

          ????this.socket.emit('register',?connector);

          ????this.socket.on('register?done',?function()?{
          ??????window.ChatroomDOM.renderAfterRegister();
          ????});

          ????this.socket.on('data',?function(res)?{
          ??????window.ChatroomDOM.renderData(res);
          ????});

          ????this.socket.on('disconnect',?function()?{
          ??????window.ChatroomDOM.renderAfterLogout();
          ????});
          ??}
          ??//?...
          }

          //?服務(wù)器端
          var?io?=?socketIo(httpServer);

          io.on('connection',?(socket)?=>?{
          ??socket.on('register',?function(connector)?{
          ????chatRoom.onConnect(connector);

          ????io.emit('register?done');

          ????var?data?=?chatRoom.getDatas();
          ????io.emit('data',?{?data?});
          ??});

          ??socket.on('chat',?function(message)?{
          ????chatRoom.receive(message);

          ????var?data?=?chatRoom.getDatas();
          ????io.emit('data',?{?data?});
          ??});
          });

          響應(yīng)格式如下:

          Server-Sent Events(SSE)

          傳統(tǒng)意義上服務(wù)器端不會(huì)主動(dòng)推送給客戶端消息,一般都是客戶端主動(dòng)去請(qǐng)求服務(wù)器端獲取最新的數(shù)據(jù)。SSE就是一種可以主動(dòng)從服務(wù)端推送消息的技術(shù)。

          SSE的本質(zhì)其實(shí)就是一個(gè)HTTP的長連接,只不過它給客戶端發(fā)送的不是一次性的數(shù)據(jù)包,而是一個(gè)stream流,格式為text/event-stream,所以客戶端不會(huì)關(guān)閉連接,會(huì)一直等著服務(wù)器發(fā)過來的新的數(shù)據(jù)流,視頻播放就是這樣的例子。

          • SSE 使用 HTTP 協(xié)議,現(xiàn)有的服務(wù)器軟件都支持。WebSocket 是一個(gè)獨(dú)立協(xié)議。

          • SSE 屬于輕量級(jí),使用簡單;WebSocket 協(xié)議相對(duì)復(fù)雜。

          • SSE 默認(rèn)支持?jǐn)嗑€重連,WebSocket 需要自己實(shí)現(xiàn)。

          • SSE 一般只用來傳送文本,二進(jìn)制數(shù)據(jù)需要編碼后傳送,WebSocket 默認(rèn)支持傳送二進(jìn)制數(shù)據(jù)。

          • SSE 支持自定義發(fā)送的消息類型。

          基本的使用方法,參看SSE API

          兼容性

          目前除了IE以及低版本的瀏覽器不支持,基本支持絕大多數(shù)的現(xiàn)代瀏覽器。

          代碼實(shí)現(xiàn)

          //?客戶端
          var?SSENotification?=?{
          ??source:?null,
          ??subscribe:?function()?{
          ????if?('EventSource'?in?window)?{
          ??????this.source?=?new?EventSource('/sse');

          ??????this.source.addEventListener('message',?function(res)?{
          ????????const?d?=?res.data;
          ????????window.ChatroomDOM.renderData(JSON.parse(d));
          ??????});
          ????}
          ????return?this.unsubscribe;
          ??},
          ??unsubscribe:?function?()?{
          ????this.source?&&?this.source.close();
          ??}
          }

          //?服務(wù)器端
          router.get('/sse',?function(req,?res)?{
          ??const?connectors?=?chatRoom.getConnectors();
          ??const?messages?=?chatRoom.getMessages();
          ??const?response?=?{?code:?200,?data:?{?connectors:?connectors,?messages:?messages?}?};

          ??res.writeHead(200,?{
          ????"Content-Type":"text/event-stream",
          ????"Cache-Control":"no-cache",
          ????"Connection":"keep-alive",
          ????"Access-Control-Allow-Origin":?'*',
          ??});

          ??res.write("retry:?10000\n");
          ??res.write("data:?"?+?JSON.stringify(response)?+?"\n\n");

          ??var?unsubscribe?=?Event.subscribe(function()?{
          ????const?connectors?=?chatRoom.getConnectors();
          ????const?messages?=?chatRoom.getMessages();
          ????const?response?=?{?code:?200,?data:?{?connectors:?connectors,?messages:?messages?}?};
          ????res.write("data:?"?+?JSON.stringify(response)?+?"\n\n");
          ??});

          ??req.connection.addListener("close",?function?()?{
          ????unsubscribe();
          ??},?false);
          });

          下面是控制臺(tái)的情況,注意觀察響應(yīng)類型

          詳情中注意查看請(qǐng)求類型,以及EventStream消息類型

          總結(jié)

          • 短輪詢、長輪詢實(shí)現(xiàn)成本相對(duì)比較簡單,適用于一些實(shí)時(shí)性要求不高的消息推送,在實(shí)時(shí)性要求高的場景下,會(huì)存在延遲以及會(huì)給服務(wù)器帶來更大的壓力

          • websocket目前而言實(shí)現(xiàn)成本相對(duì)較低,適合于雙工通信,對(duì)于多人在線,要求實(shí)時(shí)性較高的項(xiàng)目比較實(shí)用

          • SSE只能是服務(wù)器端推送消息,因此對(duì)于不需要雙向通信的項(xiàng)目比較適用




          粉絲福利:108本java從入門到大神精選電子書領(lǐng)取

          ???

          ?長按上方鋒哥微信二維碼?2 秒
          備注「1234」即可獲取資料以及
          可以進(jìn)入java1234官方微信群



          感謝點(diǎn)贊支持下哈?

          瀏覽 117
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中日韩欧美在线视频 | 五月天自拍视频 | 亚洲www在线观看 | 亚洲草逼图| 日日撸夜夜爱 |