一、服務器推送理解
首先要知道為什么使用服務器推送,回答這個問題其實就是相當於回答,服務器推送的優點,可以從兩個方面來思考:
1.1 服務器推送的目的
及時的將客戶端感興趣的數據推送給它。
1.2 不是用服務器推送怎么來實現需求
不使用服務端推送,那就只能由客戶端定期對服務器發送請求,來獲取是否有需要的數據。這樣做有幾個缺點:
- 不能及時的獲取,最大延時時間為輪詢間隔。
- 浪費資源,大部分的請求,都不會得到數據
- 輪詢間隔短會對服務器造成較大的壓力。
1.3 使用服務器推送可以帶來什么好處
我理解的有兩個好處,一是及時,還有就是消耗資源穩定(消耗一個連接數)。及時很好理解,就是服務器知道數據什么時候發生變化,發生變化的時候就進行推送。而消耗資源穩定,則是因為只有一個連接,所有的數據都從這么連接發送。
1.4 使用服務器推送有哪些局限性
當然就是每一個客戶端都需要維護一個長連接,客戶端數量增多的時候,會對服務器造成較大的壓力。
1.5使用服務器推送的場景
如果滿足以下條件,那么使用它是最好的做法,若不全部滿足,則酌情考慮:
- 要求客戶端能及時的感知服務器數據的變化
- 客戶端的數量能在服務器的承受范圍內
二、服務器推送的實現
服務器推送的實現有很多種方式,這一篇博客使用EventSource來實現功能。若要實現服務器推送,需要客戶端和服務端同時對其進行支持。
2.1 客戶端代碼
客戶端代碼比較簡單,實現一個回調函數即可:
new EventSource("longConnection").onmessage = function(event) { $scope.$apply(function() { alert(event.data); }); };
回調函數中的event.data就是服務器發送的數據,此時可以對它進行其它操作。
2.2 服務端代碼
服務端的代碼一般放到一個循環中:
response.setContentType("text/event-stream;charset=UTF-8"); response.setHeader("Cache-Control","no-cache"); response.setHeader("Connection","keep-alive"); PrintWriter out=response.getWriter(); while(true){ out.print("data: " + 傳遞的數據 + "\n\n"); out.flush(); }
只要不把連接關閉,那么每一次刷新,都會講數據發送到客戶端,並觸發onmessage方法。
2.3 注意事項
2.3.1 瀏覽器的支持問題
默認IE是不支持EventSource對象的,解決辦法是引入一個js文件eventsource.min.js,但是也只能支持IE8及以上。
2.3.2 服務器數據問題
從上面的例子中可以發現,往客戶端發送數據的代碼是寫在一個死循環中,那么怎么才能實現當敏感數據發生變化時,才使其執行呢,可行性辦法有很多,現在提供一個方法,在全局范圍內使用LinkedBlockingQueue對象,當有需要發送的數據時,將數據放到隊列中,然后在循環中調用poll方法即可。
三、EventSource深入理解
如果細心的話可以發現,在服務端代碼中,寫數據的使用使用了如下格式:
out.print("data: " + 傳遞的數據 + "\n\n");
out.flush();
每次寫完刷新可不用說,重點是寫數據的時候,有一個前綴和后綴,那么這個是有什么規定嗎,還是說與頁面js代碼一一對應即可,答案是有規定的。下面使用F12對長連接進行觀察,可以看到如下內容:
具體可以使用哪些前綴,有id,data,event,retry和空白,而后綴使用兩個換行,實際上這代表着是一個空行,學習過servlet上傳的可能會理解。
- id:代表本次事件
- data:表示傳遞的數據
- event:代表觸發的事件
- retry:代表斷開重連后再次等待的時間
一般用法都是在傳遞的數據中使用json,里面存放需要調用的方法名,調用參數放在后面,這樣就可以根據不同的數據類型進行不同的處理。
同樣的客戶端代碼中使用了onmessage方法,那么是否只有這一個方法呢,其實還有額外的兩個方法,這兩個方法視情況使用 :
- onopen:這個是在連接成功后觸發的。
- onerror:這個是在出現異常的時候出發的。
四、總結
這個只是服務端推送的一種實現方式,基本能滿足要求,還有其他方式,后面會介紹。