Web 實時推送技術

1、輪詢(Polling)

  輪詢是由客戶端每隔一段時間向服務器端發起請求,查看服務器端是否產生新的數據。
優點:實現簡單,只需在原有代碼中添加定時器即可完成。
缺點:輪詢時間間隔不好設計,過長過短都不好。過長,導致用戶不能及時接收到更新的數據;過短,導致查詢請求過多,增加服務器的負擔。並且,連接數會很多,每次請求都會產生 HTTP 的 header,有效負載過低。

2、長輪詢(Long-Polling)

  長輪詢是對輪詢的改進版,當服務器收到客戶端發來的查詢請求時,如果沒有新數據就會阻塞請求,直到有新數據產生時才返回。當服務器響應或者連接超時後,客戶端再次發起請求。
優點:對 Polling 做了優化,有很好的時效性,客戶端代碼無需修改。
缺點:保持連接會消耗資源;會有連接超時的情況。

3、長連接(基於iframe)

  iframe 流的方式是在頁面中插入一個隱藏的 iframe,利用其src屬性在服務器和客戶端創建一個長連接,服務器向iframe源源不斷的傳輸數據,來實時更新頁面。
優點:瀏覽器兼容性好;消息能實時到達;
缺點:服務器維護一個長連接會增加開銷;IE、Firefox會顯示加載沒有完成,圖標會不停旋轉。

4、webSocket

  WebSocket 是一種在單個TCP連接上進行全雙工通信的協議。WebSocket 使得客戶端和服務器端的數據交換變得簡單,其允許服務端主動像客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。

WebSocket 的特點:
  • 支持雙向通信,實時性更強
  • 可以發送文本,也可以發送二進制數據
  • 減少通信量:只要建立起WebSocket 連接,就希望一直保持連接狀態。

5、Demo

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
    <div>
        <label>輪詢:</label>
        <span class="polling"></span>
    </div>
    <div>
        <label>長輪詢:</label>
        <span class="long-polling"></span>
    </div>
    <div>
        <label>長連接(iframe):</label>
        <span class="iframeText"></span>
    </div>
    <div>
        <label>WebSocket:</label>
        <span class="websocketText"></span>
    </div>
    <iframe src="/iframeStream?callback=parent.iframeCallback" style="display:none;"></iframe>
    
    <script>
        let pollingText = document.getElementsByClassName('polling')[0];
        let longPollingText = document.getElementsByClassName('long-polling')[0];
        let iframeText = document.getElementsByClassName('iframeText')[0];
        let websocketText = document.getElementsByClassName('websocketText')[0];

        let intervalTime = setInterval(function pollingFun(){
            let xhr = new XMLHttpRequest();
            xhr.open('GET', '/polling', true);
            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4 && xhr.status == 200){
                    pollingText.innerText = xhr.responseText;
                }
            }
            xhr.send();
        },1000);

        function longPolling(){
            let xhr = new XMLHttpRequest();
            xhr.open('GET', '/longpolling', true);
            xhr.timeout = 2000; // 超時
            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4){
                    if(xhr.status == 200){
                        longPollingText.innerText = xhr.responseText;
                    }
                    longPolling();
                }
            }
            xhr.ontimeout = function(){
                longPolling();
            }
            xhr.send();
        }
        longPolling();

        function iframeCallback(res){
            iframeText.innerText = res;
        }

        let socket = new WebSocket('ws://localhost:8080');
        socket.onopen = function(){
            console.log('客戶端連接成功')
            socket.send('hello');
        }
        socket.onmessage = function(event){
            websocketText.innerText = event.data;
            console.log('收到服務端消息:', event.data);
            
        }

    </script>
    
</body>
</html>

server.js

const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');

const basePath = path.join(__dirname, '/');
const server = http.createServer((req, res) => {
    if(req.url === '/longpolling'){
        // setTimeout(() => {res.end(new Date().toLocaleString())}, 10000);
        res.end(new Date().toLocaleString());
    } else if(req.url === '/'){
        fs.readFile(basePath + 'index.html',(err, data) => {
            if(err && err.code !== 'ENOENT'){
                throw err;
            }
            res.end(data);
        })

    } else if(req.url === '/polling'){
        res.end(new Date().toLocaleString());
    } else if(req.url.startsWith('/iframeStream')){
        let urlObj = url.parse(req.url, true);
        setInterval(() => {
            res.write(`<script>${urlObj.query.callback}('${new Date().toLocaleString()}')</script>`)
        }, 1000)
    }
});

server.listen(8000);

WebSocket

const WebSocketServer = require('ws').Server;  // npm install ws

let wss = new WebSocketServer({port:8080});

let socket;

wss.on('connection', function(ws){
    socket = ws;
    ws.on('message', function(message){
        socket.send(new Date().toLocaleString());
        setInterval(() => {
            socket.send(new Date().toLocaleString());
        }, 1000)
    })
    ws.on('close', (message) =>{})
})

參考:Web 實時推送技術的總結(https://mp.weixin.qq.com/s/fnRAqxA1JCWppFGBAHwreA

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章