連夜擼了一個簡易聊天室
點擊上方藍色“小哈學Java”,選擇“設(shè)為星標”
回復(fù)“資源”獲取獨家整理的學習資料!


分不清輪詢、長輪詢?不知道什么時候該用websocket還是SSE,看這篇就夠了。
所謂的“實時推送”,從表面意思上來看是,客戶端訂閱的內(nèi)容在發(fā)生改變時,服務(wù)器能夠?qū)崟r地通知客戶端,進而客戶端進行相應(yīng)地反應(yīng)。客戶端不需要主觀地發(fā)送請求去獲取自己關(guān)心的內(nèi)容,而是由服務(wù)器端進行“推送”。
注意上面的推送二字打了引號,這就意味著在現(xiàn)有的幾種實現(xiàn)方式中,并不是服務(wù)器端主動地推送,而是通過一定的手段營造了一種實時的假象。就目前現(xiàn)有的幾種技術(shù)而言,主要有以下幾類:
客戶端輪詢:傳統(tǒng)意義上的輪詢(Short Polling) 服務(wù)器端輪詢:長輪詢(Long Polling) 全雙工通信:Websocket 單向服務(wù)器推送:Server-Sent Events(SSE)
https://github.com/Rynxiao/mini-chatroom

輪詢(Short Polling)
不斷的發(fā)送和關(guān)閉請求,對服務(wù)器的壓力會比較大,因為本身開啟Http連接就是一件比較耗資源的事情 輪詢的時間間隔不好控制。如果要求的實時性比較高,顯然使用短輪詢會有明顯的短板,如果設(shè)置interval的間隔過長,會導(dǎo)致消息延遲,而如果太短,會對服務(wù)器產(chǎ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);
??}
}


長輪詢(Long Polling)
對于內(nèi)容變化的輪詢由客戶端改成了服務(wù)器端(客戶端會在連接中斷之后,會再次發(fā)送請求,對比短輪詢來說,大大減少了發(fā)起連接的次數(shù)) 客戶端只會在數(shù)據(jù)改變時去作相應(yīng)的改變,對比短輪詢來說,并不是全盤接收
代碼實現(xiàn)
// 客戶端
var?LongPollingNotification = {
????// ....
????subscribe: function() {
??????var?that = this;
??????// 設(shè)置超時時間
??????Request.getV2Datas(this.getKey(),{ timeout: 10000?}).then(function(res) {
????????var?data = res.data;
????????window.ChatroomDOM.renderData(res);
????????// 成功獲取數(shù)據(jù)后會再次發(fā)送請求
????????that.subscribe();
??????}).catch(function?(error) {
????????// timeout 之后也會再次發(fā)送請求
????????that.subscribe();
??????});
??????return?this.unsubscribe;
????}
????// ....
}客戶端第一次會帶一個空的key值,這次會立即返回,獲取新內(nèi)容,服務(wù)器端將計算出的contentKey返回給客戶端 然后客戶端發(fā)送第二次請求,帶上第一次返回的contentKey作為key值,然后進行下一輪的比較 如果兩次的key值相同,就會hold請求,進行內(nèi)部輪詢,如果期間有新內(nèi)容或者客戶端timeout,就會斷開連接 重復(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 },
??});
});// 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);
在頁面中嵌入一個iframe,地址指向輪詢的服務(wù)器地址,然后在父頁面中放置一個執(zhí)行函數(shù),比如 execute(data)當服務(wù)器有內(nèi)容改變時,會向iframe發(fā)送一個腳本 通過發(fā)送的腳本,主動執(zhí)行父頁面中的方法,達到推送的效果
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è)計的目的主要是為了減少傳統(tǒng)輪詢時http連接數(shù)量的開銷 建立在TCP協(xié)議之上,握手階段采用 HTTP 協(xié)議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務(wù)器 與HTTP兼容性良好,同樣可以使用80和443端口 沒有同源限制,客戶端可以與任意服務(wù)器通信 可以發(fā)送文本,也可以發(fā)送二進制數(shù)據(jù)。 協(xié)議標識符是 ws(如果加密,則為wss),服務(wù)器網(wǎng)址就是 URL

兼容性

代碼實現(xiàn)
// 客戶端
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 });
??});
});
Server-Sent Events(SSE)
SSE的本質(zhì)其實就是一個HTTP的長連接,只不過它給客戶端發(fā)送的不是一次性的數(shù)據(jù)包,而是一個stream流,格式為text/event-stream,所以客戶端不會關(guān)閉連接,會一直等著服務(wù)器發(fā)過來的新的數(shù)據(jù)流,視頻播放就是這樣的例子。
SSE 使用 HTTP 協(xié)議,現(xiàn)有的服務(wù)器軟件都支持。WebSocket 是一個獨立協(xié)議。 SSE 屬于輕量級,使用簡單;WebSocket 協(xié)議相對復(fù)雜。 SSE 默認支持斷線重連,WebSocket 需要自己實現(xiàn)。 SSE 一般只用來傳送文本,二進制數(shù)據(jù)需要編碼后傳送,WebSocket 默認支持傳送二進制數(shù)據(jù)。 SSE 支持自定義發(fā)送的消息類型。

兼容性

代碼實現(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);
});

總結(jié)
短輪詢、長輪詢實現(xiàn)成本相對比較簡單,適用于一些實時性要求不高的消息推送,在實時性要求高的場景下,會存在延遲以及會給服務(wù)器帶來更大的壓力 websocket目前而言實現(xiàn)成本相對較低,適合于雙工通信,對于多人在線,要求實時性較高的項目比較實用 SSE只能是服務(wù)器端推送消息,因此對于不需要雙向通信的項目比較適用
參考連接
https://tools.ietf.org/html/rfc6455 https://developer.mozilla.org/en-US/docs/Web/API/WebSocket https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events http://www.ruanyifeng.com/blog/2017/05/websocket.html https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html https://juejin.im/post/6844903955240058893
題外話: 目前小哈正在個人博客(新搭建的網(wǎng)站,域名就是犬小哈的拼音)?www.quanxiaoha.com?上更新《Go語言教程》、《Gin Web框架教程》,畢竟Go自帶天然的并發(fā)優(yōu)勢,后端的同學還是要學一下的,這個教程系列小哈會一直更新下去,歡迎小伙伴們訪問哦~
END
有熱門推薦?
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點“在看”,關(guān)注公眾號并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)
評論
圖片
表情

