在瀏覽某些網頁的時候,例如 WebQQ
、京東在線客服服務、CSDN私信消息等相似的狀況下,咱們能夠在網頁上進行在線聊天,或者即時消息的收取與回覆,可見,這種功能的需求由來已久,而且應用普遍。javascript
網上關於這方面的文章也能搜到一大堆,不過基本上都是理論,真正可以運行的代碼不多,原理性的東西我就不當搬運工了,本文主要是貼示例代碼,最多在代碼中穿插一點便於理解,本文主要的示例代碼基於 javascript
,服務端基於 nodejs
的 koa(1/2)
框架實現。html
Web
端 常見的消息推送實際上大多數都是模擬推送,之因此是模擬推送,是由於這種實現並非服務器主動推送,本質依舊是客戶端發起請求,服務端返回數據,起主動做用的是客戶端。前端
實現上最簡單的一種模擬推送方法,原理就是客戶端不斷地向服務端發請求,若是服務端數據有更新,服務端就把數據發送回來,客戶端就能接收到新數據了。vue
一種實現的示例以下:html5
上述代碼,設置定時任務,每隔 2s
使用 ajax
發起一次請求,客戶端根據服務端返回的數據來進行決定執行對應的操做,除了發送 ajax
,你還可使用 fetch
:java
引伸:
fetch
目前的瀏覽器支持度還很低,因此在實際生產環境中使用的時候,最好添加一些polyfill
,一種墊片使用順序示例以下:
-es5
的polyfill
— es5-shim
-Promise
的polyfill
— es6-promise -IE8+
-fetch
的polyfill
— fetch -IE10+
node
若是你在使用某種框架,例如 vue
或者 angular
,那麼你一樣可使用這些框架自帶的請求方法,總之基於頁面的友好訪問性,在發送請求的同時不要刷新頁面就好了。git
優勢:es6
先後端程序都很容易編寫,沒什麼技術難度github
缺點:
這種方法由於須要對服務器進行持續不斷的請求,就算你設置的請求間隔時間很長,但在用戶訪問量比較大的狀況下,也很容易給服務器帶來很大的壓力,並且絕大部分狀況下都是無效請求,浪費帶寬和服務器資源,通常不會用於實際生產環境的,本身知道一下就好了。
相比於上一種實現,長輪詢一樣是客戶端發起請求,服務端返回數據,只不過不一樣的是,在長輪詢的狀況下,服務器端在接到客戶端請求以後,若是發現數據庫中的數據並無更新或者不符合要求,那麼就不會當即響應客戶端,而是 hold
住此次請求,直到符合要求的數據到達或者由於超時等緣由纔會關閉鏈接,客戶端在接收到新數據或者鏈接被關閉後,再次發起新的請求。
爲了節約資源,一次長輪詢的週期時間最好在 10s ~ 25s
左右,長鏈接也是實際生產環境中,被普遍運用於實時通訊的技術。
客戶端代碼以下:
想要在鏈接斷開或發生錯誤的時候,再次發起請求鏈接,實現也很簡單,如下問使用 fetch
實現示例:
一種較爲直觀的服務器 hold
住鏈接的實現以下:
還有一種方法,不過這種純粹是爲了 hold
住而 hold
住,能夠做爲上一種方法的輔助,解決諸如服務端進行瘋狂查詢數據庫的操做,相似於 Java
中的 Thread.sleep()
操做
若是你如今的 Nodejs
版本支持 ES6
中的 Generator
的話,那麼還能夠這樣(koa1
環境, Generator
寫法):
若是你如今的 Nodejs
版本支持 ES7
中的 async/await
的話,,那麼還有一種 hold
住鏈接的方法可供選擇(koa2
環境):
優勢:
儘管長輪詢不可能作到每一次的響應都是有用的數據,由於服務器超時或者客戶端網絡環境的變化,以及服務端爲了更好的分配資源而自動在一個
心跳
週期的末尾斷掉鏈接等緣由,而致使長輪詢不可能一直存在,必需要不斷地進行斷開和鏈接操做,但不管如何,相比於短輪詢來講,長輪詢耗費資源明顯小了不少
缺點:
服務器
hold
鏈接依舊會消耗很多的資源,特別是當鏈接數很大的時候,返回數據順序無保證,難於管理維護。
這種是基於 iframe
或者 script
實現的,主要原理大概就是在主頁面中插入一個隱藏的 iframe(script)
,而後這個 iframe(script)
的 src
屬性指向服務端獲取數據的接口,由於是iframe(script)
是隱藏的,並且 iframe(script)
的 刷新也不會致使 主頁面刷新,因此能夠爲這個 iframe(script)
設置一個定時器,讓其每隔一段時間就朝服務器發送一次請求,這樣就能得到服務端的最新數據了。
先說一下 利用 script
的長鏈接:
前端實現:
後端實現:
主要是在前端,一共兩條 script
腳本,大體左右就是在必定的時間間隔內(示例爲 3s
)就動態地在頁面中增刪一個連接爲用於請求後端數據的 script
腳本。
後端則返回一段字符串,這段字符串在返回前端時,有一個 callback
字段調用前端的代碼,相似於 jsonp
的請求。
注意:修改一個已經執行過的
script
腳本的src
屬性是沒什麼卵用的,修改以後,最多在頁面的DOM
上發生一些變化,而瀏覽器既不會發請求,也不會執行腳本,因此這裏採用動態增刪整個script
標籤的作法。
能夠看到,這種方法其實與短輪詢沒什麼區別,惟一的區別在於短輪詢保證每次請求都能收到響應,但上述示例的長鏈接不必定每次都能獲得響應,若是下一次長鏈接開始請求,上一次鏈接還沒獲得響應,則上一次鏈接將被終止。
固然,若是你想長鏈接每次也都能保證獲得響應也是能夠的,大體作法就是在頁面中插入不止一條 script
標籤,每條標籤對應一個請求,等到當前請求到達再決定是否移除當前 script
標籤。
若是想要獲得有序的數據響應,則還能夠將 setInterval
換成遞歸調用,例如:
使用 iframe
的方式與此相似,就不贅述了,不過須要注意的是, iframe
可能存在跨域的狀況,可能會比 script
方式麻煩一些。
WebSoket
是 HTML5
新增的 API
,具體介紹以下(來源w3c菜鳥教程)
WebSocket是HTML5開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協議。
在WebSocket API中,瀏覽器和服務器只須要作一個握手的動做,而後,瀏覽器和服務器之間就造成了一條快速通道。二者之間就直接能夠數據互相傳送。
瀏覽器經過 JavaScript 向服務器發出創建 WebSocket 鏈接的請求,鏈接創建之後,客戶端和服務器端就能夠經過 TCP 鏈接直接交換數據。
當你獲取 Web Socket 鏈接後,你能夠經過 send() 方法來向服務器發送數據,並經過 onmessage 事件來接收服務器返回的數據。
上面所提到的短輪詢、長輪詢、長鏈接,本質都是單向通訊,客戶端主動發起請求,服務端被動響應請求,但 WebSocket
則已是全雙工通信了,也就是說不管是客戶端仍是服務端都能主動向對方發起響應,服務器具有了真正的 推送
能力。
一段簡單的 客戶端 WebSocket
代碼示例以下:
想要讓客戶端的 WebSocket
可以鏈接上服務器,服務端必需要具有可以響應 WebSocket
類型的請求才行,通常的服務器是沒有自帶這種能力的,因此必需要對服務器端程序代碼作出些改變。
本身封裝服務器端響應 WebSocket
的代碼可能會涉及到很底層的東西,因此通常都是使用第三方封裝好的庫,基於nodejs
的 WebSocket
庫有不少,ws 功能簡單, API
形式更貼近於原生,大名鼎鼎的 socket.io 是與 Nodejs
聯手開發,功能齊全,被普遍運用於遊戲、實時通信等應用。
如下給出一種基於 socket.io 實現 簡單客戶端和服務端通訊的示例:
客戶端:
服務端實現:
效果以下:
注、websocket是javaweb實現即時消息推送最佳方案,可是須要服務器jdk在版本7以上支持,低版本瀏覽器還不支持,因此要支持低版本即時消息推送還須要選擇另一種方法。
使用反向ajax框架DWR
DWR(Direct Web RemoTIng)是一個Web遠程調用AJAX擴展框架,經過DWR客戶端的JavaScript能夠直接調用Web服務器上的JavaBean類的方法,解決了原有AJAX應用必需請求HTTP控制組件(如Servlet,Struts的AcTIon等)才能調用服務器端業務類的方法,從而簡化了AJAX應用的開發。使用DWR能夠不須要編寫複雜的控制層組件。
1.2 DWR反向AJAX技術
正常狀況下,DWR調用服務器端的JavaBean對象方法使用正向請求/響應模式,也稱爲拉模式(Pull Model),由客戶端JavaScript調用JavaBean方法,返回結果經過回調方法更新頁面上的HTML元素,實現監控數據的顯示。這種正向模式符合通常的管理系統應用,但對監控系統實時性要求較高的應用卻力不從心。而反向模式即推模式(Push Model),是適應監控系統的最佳方式,由服務器組件將取得的監控數據推送到Web客戶端,不須要客戶端主動請求,而是被動接收。於是無需進行Web層進行頁面刷新,便可實現數據更新顯示。
最新版本的DWR 2.X增長了反向(Reverse AJAX)功能,經過反向AJAX功能,服務器端的JavaBean對象能夠將取得的數據直接推送到指定的客戶端頁面,寫到指定的HTML元素內,這個過程不須要客戶端進行任何的請求操做。