web 推播技術探討

最近筆者單位內要開發投票系統,但又需要有一個主控台能夠控制每個投票Client進行新一輪投票,這幾天看了設計成品之後,發現一開始計沒有使用好的設計,導致後面問題一堆、不僅佔用頻寬大,出錯機率也高,所以剛好來跟大家討論一下網路推播的相關技術。

web推播技術

基本上web架構不是設計來長時間連線使用,長時間連線一般我們會使用TCP socket等長時間連線方式來設計,但近年來許多web長時間連線需求越來越多,因此web技術也因應這類需求發展出了幾種技術:

  • 輪詢 polling
  • 長輪詢 long-polling
  • iframe stream(中文不知怎麼翻,先用英文)

polling 輪詢

每隔一段時間就由瀏覽器主動向伺服器發起連線,這次筆者單位內的設計就是採用此方式。每次輪詢就需要送一次完整的HTTP Request,耗流量也耗CPU效率,若client一多輪詢時間又短時,很容易造成伺服器癱瘓,時間長,使用者取得的資訊又會有落差。但好處是程式撰寫簡單。通常會使用setTimeout 或 setInterval來作設計,如:


// 1.html
<div id="clock"></div>
<script>
    let clockDiv = document.getElementById('clock');
    setInterval(function(){
        let xhr = new XMLHttpRequest;
        xhr.open('GET','/clock',true);
        xhr.onreadystatechange = function(){
            if(xhr.readyState == 4 && xhr.status == 200){
                console.log(xhr.responseText);
                clockDiv.innerHTML = xhr.responseText;
            }
        }
        xhr.send();
    },1000);
</script>

長輪詢 long polling

long polling 是對pooling的改進,client發送請求給server後,若沒有新訊息,就會開始等待,直到有新訊息才會返回client端。優點是較polling有較好的效率,但缺點便是保持連線會損耗資源及佔用server的連線數,降低可服務的人數,client端也容易會有連線超時的問題發生。範例:

<div id="clock"></div>
<script>
let clockDiv = document.getElementById('clock')
function send() {
  let xhr = new XMLHttpRequest()
  xhr.open('GET', '/clock', true)
  xhr.timeout = 2000 // timeout 時間,單位是:毫秒
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {  //成功
        clockDiv.innerHTML = xhr.responseText
      }
      send() //不管成功或失敗,都繼續發送下一次請求
    }
  }
  xhr.ontimeout = function() {
    send()
  }
  xhr.send()
}
send()
</script>

iframe stream

iframe stream 是在網頁中插入隱藏的iframe,利用iframe 的src來接收伺服器的資訊,並更新網頁資訊。優點是瀏覽器相容性好,接收即時訊息也快,缺點是有的瀏覽器client不容許使用iframe(資安問題),讀取資料時,游標會變成讀取狀態(一直轉)。

<body>
    <div id="clock"></div>
    <iframe src="/clock" style="display:none"></iframe>
</body>

// client 端
//iframe流 
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.get('/clock', function(req, res) {
  setInterval(function() {
    let date = new Date().toLocaleString()
    res.write(`
       <script type="text/javascript">
         parent.document.getElementById('clock').innerHTML = "${date}";//改变父窗口dom元素
       </script>
     `)
  }, 1000)
})
app.listen(8080)

web socket

上述三種方式是比較舊的web長連線方式,優缺點已經說明,目前較進步的技術是採用HTML5 websocket方式來作長連線。將TCP的socket套用在web上,透過瀏覽器與server建立socket連線,並透過此socket來傳輸資料,由於是採用socket技術,因此不管是client端或是server端,都可以發起請求,要求一方提供資料,概念與傳統TCP的 socket通訊一樣,封包資料量小,同時可以傳送二進位資料或文字資料。

通常最大可以開:1024 個連線。(Too many open files)可以用 ulimia -a 指令查看限制。

// websocket.html
<div id="clock"></div>
<script>
let clockDiv = document.getElementById('clock')
let socket = new WebSocket('ws://localhost:9999')
//連結成功後就執行函數
socket.onopen = function() {
  console.log('客户端連接成功')
  //向伺服器發送消息
  socket.send('hello')
}
//绑定事件是用加属性的方式
socket.onmessage = function(event) {
  clockDiv.innerHTML = event.data
  console.log('收到伺服器回應', event.data)
}
</script>