淺析web端的消息推送原理


轉載本文需註明出處:EAWorld,違者必究。

引言:

在互聯網高速發展的時代裏,web應用大有取代桌面應用的趨勢,沒必要再去繁瑣的安裝各類軟件,只需一款主流瀏覽器便可完成大部分常規操做,這些緣由都在吸引着軟件廠商和消費者。而隨着各大廠商瀏覽器版本的迭代,前端技術的不斷革新,消息推送用到的場景也愈來愈多了。

收發郵件提醒,在線IM聊天,自動化辦公提示等等,web系統里老是能見到消息推送的應用。消息推送用好了能加強用戶體驗,用很差則會起相反的效果。在司空見慣的使用過程當中,有沒有對其中的原理產生興趣呢?實現消息推送有N種解決方案,本文針對其中的幾種,進行原理性的講解並附有簡單的代碼實現。

目錄:

1、什麼是消息推送
2、web端的消息推送
3、實現個性化的推送
 javascript

1、什麼是消息推送

 

  • 經典場景1



當我在官網觀望猶豫時,忽然看到了上面消息,一位神祕的徐老闆居然爆出了麻痹戒指!!個人天,因而我果斷開始了遊戲!這消息很及時!
 html

  • 經典場景2



當我拿起手機不知幹嗎時收到了這條招女婿的消息.......瞬間來了精神

上述兩種場景,是生活中很常見的場景,經過圖文描述,應該已經清楚了推送的場景,也引出了兩大推送種類,web端消息推送和移動端消息推送。接下來對消息推送進行具體的解釋。

概念:

消息推送(Push)指運營人員經過本身的產品或第三方工具對用戶當前網頁或移動設備進行的主動消息推送。用戶能夠在網頁上或移動設備鎖定屏幕和通知欄看到push消息通知。以此來實現用戶的多層次需求,使得用戶可以本身設定所須要的信息頻道,獲得即時消息,簡單說就是一種定製信息的實現方式。咱們平時瀏覽郵箱時忽然彈出消息提示收到新郵件就屬於web端消息推送,在手機鎖屏上看到的微信消息等等都屬於APP消息推送。 
 前端

2、web端的消息推送


這一章節主要對幾種消息推送的方式進行原理性的講解,並貼出簡單實現的代碼。

主要介紹其中的五種實現方式:短輪詢、Comet、Flash XMLSocket、Server-sent、WebSocket。

一、短輪詢

指在特定的的時間間隔(如每10秒),由瀏覽器對服務器發出HTTP request,而後由服務器返回最新的數據給客戶端的瀏覽器。瀏覽器作處理後進行顯示。不管後端此時是否有新的消息產生,都會進行響應。字面上看,這種方式是最簡單的。這種方式的優勢是,後端編寫很是簡單,邏輯不復雜。可是缺點是請求中大部分中是無用的,浪費了帶寬和服務器資源。總結來講,簡單粗暴,適用於小型(偷懶)應用。

基於Jquery的ajax前端代碼:

<body>
    <div id="push"></div>
    <script>
        $(function () {
            setInterval(function () {
                getMsg(function (res) {
                    $("#push").append("<p>" + res +"</p>");
                })
            },10000);
        });

        function getMsg(handler){
            $.ajax({
                url:"/ShortPollingServlet",
                type:"post",
                success:function (res) {
                    handler(res)
                }
            });
        }
</script>
</body>

servlet簡單實現後端代碼:

public class ShortPollingServlet extends HttpServlet {

    public static int count = 0;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //模擬業務代碼
        count++;
        response.getWriter().print("msg:" + count );
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

二、Comet

包括了長輪詢和長鏈接,長輪詢是客戶端向服務器發送Ajax請求,服務器接到請求後hold住鏈接,直到有新消息才返回響應信息並關閉鏈接,客戶端處理完響應信息後再向服務器發送新的請求;長鏈接是在頁面中的iframe發送請求到服務端,服務端hold住請求並不斷將須要返回前端的數據封裝成調用javascript函數的形式響應到前端,前端不斷收到響應並處理。Comet的實現原理和短輪詢相比,很明顯少了不少無用請求,減小了帶寬壓力,實現起來比短輪詢複雜一丟丟。比用短輪詢的同窗有夢想時,就能夠用Comet來實現本身的推送。

長輪詢的優勢很明顯,在無消息的狀況下不會頻繁的請求,耗費資小而且實現了服務端主動向前端推送的功能,可是服務器hold鏈接會消耗資源,返回數據順序無保證,難於管理維護。WebQQ(好像掛了)就是這樣實現的。

基於Jquery的ajax前端代碼:

<body>
    <div id="push"></div>
    <script>
        $(function () {
            getMsg();
        });

        function getMsg() {
            $.ajax({
                url:"/LongPollingServlet",
                type:"post",
                success:function (res) {
                    $("#push").append("<p>" + res +"</p>");
                    getMsg();
                }
            });
        }
</script>
</body>

servlet簡單實現後端代碼:

public class LongPollingServlet extends HttpServlet {

    public static int count = 0;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        count++;
        //睡眠時間模擬業務操做等待時間
        double random = Math.round(Math.random()*10);
        long sleepTime = new Double(random).longValue();
        try{
            Thread.sleep(sleepTime*1000);
            response.getWriter().print("msg:" + count + " after " + sleepTime + "seconds servicing");
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

長鏈接優勢是消息便是到達,不發無用請求,管理起來也相對方便。缺點是服務端維護一個長鏈接會增長開銷。好比Gmail聊天(沒用過)就是這樣實現的。

基於Jquery的ajax前端代碼:

<head>
  <title>pushPage</title>
  <script type="text/javascript">
    function loadData(msg) {
      var newChild = document.createElement("p");
      newChild.innerHTML = msg;
      document.getElementById("push").appendChild(newChild);
    }
</script>
</head>
<body>
<div id="push"></div>
<iframe src="/LongConnServlet" frameborder="0" name="longConn"></iframe>
</body>

servlet簡單實現後端代碼:

public class LongConnServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        boolean flag = true;
        int i = 0;
        while (flag){
            try {
                //模擬每1秒查詢一次數據庫,看是否有新的消息能夠推送
                Thread.sleep(1*1000);
            }catch (Exception e){
                e.printStackTrace();
            }

            String pushMsg = "push msg : " + i;
            response.setContentType("text/html;charset=GBK");
            response.getWriter().write("<script type='text/javascript'>parent.loadData('" + pushMsg + "')</script>");
            response.flushBuffer();
            i++;
            if(i==5){
                flag = false;
            }
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

三、Flash XMLSocket

在 HTML 頁面中內嵌入一個使用了 XMLSocket 類的 Flash 程序。JavaScript 經過調用此 Flash 程序提供的socket接口與服務器端的socket進行通訊。JavaScript 在收到服務器端以 XML 格式傳送的信息後能夠很容易地控制 HTML 頁面的內容顯示。


原理示意圖(引用http://t.cn/Ex6CYHk)

利用Flash XML Socket實現」服務器推」技術前提:

(1)Flash提供了XMLSocket類,服務器利用Socket向Flash發送數據;
(2)JavaScript和Flash的緊密結合JavaScript和Flash能夠相互調用。

優勢是實現了socket通訊,再也不利用無狀態的http進行僞推送。可是缺點更明顯:

1.客戶端必須安裝 Flash 播放器;
2.由於 XMLSocket 沒有 HTTP 隧道功能,XMLSocket 類不能自動穿過防火牆;
3.由於是使用套接口,須要設置一個通訊端口,防火牆、代理服務器也可能對非 HTTP 通道端口進行限制。

這種方案在一些網絡聊天室,網絡互動遊戲中已獲得普遍使用。不進行代碼示例。(可參考http://t.cn/aezSch)

四、Server-sent

服務器推指的是HTML5規範中提供的服務端事件EventSource,瀏覽器在實現了該規範的前提下建立一個EventSource鏈接後,即可收到服務端的發送的消息,實現一個單向通訊。客戶端進行監聽,並對響應的信息處理顯示。該種方式已經實現了服務端主動推送至前端的功能。優勢是在單項傳輸數據的場景中徹底知足需求,開發人員擴展起來基本不須要改後端代碼,直接用現有框架和技術就能夠集成。

基於HTML5的Server-sent事件:

<head>
    <title>Title</title>
    <script>
        var source = new EventSource("/ServerSentServlet");//建立一個新的 EventSource對象,
        source.onmessage = function (evt) {//每接收到一次更新,就會發生 onmessage事件
            var newChild = document.createElement("p");
            newChild.innerHTML = evt.data;
            document.getElementById("push").appendChild(newChild);
        }
</script>
</head>
<body>
    <div id="push"></div>
</body>

servlet簡單實現後端代碼:

public class ServerSentServlet extends HttpServlet {

    public static int count = 0;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        count++;
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Type", "text/event-stream");//設置服務器端事件流
        response.setHeader("Cache-Control","no-cache");//規定不對頁面進行緩存
        response.setHeader("Pragma","no-cache");
        response.setDateHeader("Expires",0);
        PrintWriter pw = response.getWriter();
        pw.println("retry: 5000"); //設置請求間隔時間
        pw.println("data: " + "msg:" + count +"\n\n");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

五、WebSocket

WebSocket是HTML5下一種新的協議,是基於TCP的應用層協議,只須要一次鏈接,即可以實現全雙工通訊,客戶端和服務端能夠相互主動發送消息。客戶端進行監聽,並對響應的消息處理顯示。這個技術相信基本都據說過,就算沒寫過代碼,也大概知道幹嗎的。經過名字就能知道,這是一個Socket鏈接,一個能在瀏覽器上用的Socket鏈接。是HTML5標準中的一個內容,瀏覽器經過javascript腳本手動建立一個TCP鏈接與服務端進行通信。優勢是雙向通訊,均可以主動發送消息,既能夠知足「問」+「答」的響應機制,也能夠實現主動推送的功能。缺點就是編碼相對來講會多點,服務端處理更復雜(我以爲當一條有情懷的鹹魚就應該用這個!)。

前端代碼:

<body>
    <div id="push"></div>
</body>
<script>
    $(function () {
        var webSocket = new WebSocket("ws://localhost:8080/ws");
        webSocket.onmessage = function (ev) {
            $("#push").append("<p>" + ev.data +"</p>");
        }
    })
</script>

基於註解簡單實現後端代碼:

@ServerEndpoint("/ws")
public class MyWebSocket {

    private Session session;

    public MyWebSocket() {

    }

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        System.out.println("someone connect");
        int count = 1;
        while (count<=5){
            //睡眠時間模擬業務操做等待時間
            double random = Math.round(Math.random()*10);
            long sleepTime = new Double(random).longValue();
            try {
                Thread.sleep(sleepTime*1000);
                session.getBasicRemote().sendText("msg:" + count +" from server after" + sleepTime + " seconds");
            }catch (Exception e){
                e.printStackTrace();
            }
            count++;
        }

    }

    @OnError
    public void onError(Throwable t){
        System.out.println("something error");
    }
}

以上是對五種推送方式原理的簡單講解和代碼的實現。
 java

3、實現個性化的推送 

上面說了不少原理,也給出了簡單的代碼實現,可是在實際生產過程當中,確定不能用上面的代碼,針對本身系統的應用場景選擇合適的推送方案纔是合理的,所以最後簡單說一下實現個性化推送的兩種方式。第一種很簡單,直接使用第三方實現的推送,無需複雜的開發運維,直接可使用。第二種就是本身封裝,能夠選擇現在較爲火熱的WebSocket來實現系統的推送。



一、第三方

在這裏推薦一個第三方推送平臺,GoEasy。

推薦理由是GoEasy的理念符合咱們的選擇(可參考http://t.cn/Ex6jg3q):

(1)更簡單的方式將消息從服務器端推送至客戶端
(2)更簡單的方式將消息從各類客戶端推送至客戶端

GoEasy具體的使用方式這裏再也不贅述,詳見官網。對於後端後端開發者,可直接使用Rest方式調用推送,對於前端或web開發者,能夠從web客戶端用javascript腳本進行調用推送。

二、封裝本身的推送服務

若是是一個老系統進行擴展,那麼更推薦使用Server-sent,服務端改動量不會很大。若是是新系統,更推薦websocket,實現的功能功能更全面。

咱們以websocket爲例,再也不貼出具體的代碼實現。

咱們若是須要使用websocket技術實現本身的推送服務,須要注意哪些點,或者說須要踩哪些坑呢,本文最後列出幾點供你們參考:

長鏈接的心跳處理;
從WebSocket中獲取HttpSession進行用戶相關操做;
服務端調優實現高併發量client同時在線;
服務端維持多用戶的狀態;
羣發消息;
等等等….

最後貼出上述代碼的git庫地址,全部demo都可運行。環境爲jdk1.8+tomcat8。http://t.cn/Ex6TRVZ


關於做者:徐曉明,普元開發工程師,畢業於遼寧科技大學,專一於使用移動開發平臺開發app,負責中國郵政集團移動平臺項目郵我行app開發和後臺開發運維工做。

關於EAWorld:微服務,DevOps,數據治理,移動架構原創技術分享。長按二維碼關注!git

相關文章
相關標籤/搜索