天天看點

【nodejs】不用WebSocket模拟即時通訊的幾種方式(Comet與SSE)

背景

  • 最近學了websocket,感覺很有趣,特别是不用websocket也能模拟出即時通訊效果的騷操作非常牛b。

Comet

  • 這個Comet翻譯成中文叫

    伺服器推

    技術。
  • 傳統模式是用戶端發請求,服務端傳回就結束了,但這明顯不能滿足即時報價,即時通訊之類需求。
  • 而comet技術解決這個痛點一般采用2種方式:

    1、在浏覽器端安裝插件,基于套接口傳送資訊,或是使用 RMI、CORBA 進行遠端調用。

    這個本文不做研究,簡單說就是浏覽器裝flash,flash裡面有個XMLSocket接口,基于這個來進行即時通訊。或者通過 java.net.Socket 或 java.net.DatagramSocket 或 java.net.MulticastSocket 建立與伺服器端的套接口連接配接,或者還可以去翻翻微軟的sliverlight,應該也有這樣的插件。

    2、基于http的伺服器推技術。

    這個主要分為三種:輪詢,長輪詢,iframe流。

一、輪詢

  • 輪詢原理很簡單,正常人都能想到,就是開個定時器,然後定時問一下伺服器有沒有新資料。
  • 代碼:

用戶端

setInterval(()=>{
    let xhr= new XMLHttpRequest()
    xhr.open('GET','http://localhost:8080/clock', true)
    xhr.onreadystatechange=function(){
        if(xhr.readyState===4&&xhr.status===200){
            document.querySelector('.clock').innerHTML=xhr.responseText
        }
    }
    xhr.send()
},1000)
           

服務端

let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.get('/clock',(req,res)=>{
    res.end(new Date().toLocaleTimeString());
})
app.listen(8080)
           
  • 這個缺點就比較明顯,很浪費性能和帶寬。

二、長輪詢

  • 由于輪詢有很大缺點,是以就有了長輪詢。
  • 長輪詢的原理其實就是利用了http在請求發出去後會有個等待響應時間。比如沒有新的報價之類的資料更新,那麼請求就一直放我這不進行傳回,或者延遲一段時間傳回。但是一旦資料有變動,服務端立即傳回相應,用戶端收到響應後渲染頁面。
  • 代碼:

服務端

let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.get('/clock',(req,res)=>{
    let timer = setInterval(() => {
        let date = new Date()
        let seconds = date.getSeconds()
        if(seconds%5===0){//模拟資料變化更新滿足條件
            res.end(date.toLocaleString())
            clearInterval(timer)//同時把定時器清除
        }
    }, 1000);
})
app.listen(8080)
           

用戶端

function longConnect(){
    let xhr= new XMLHttpRequest()
    xhr.open('GET','http://localhost:8080/clock', true)
    xhr.onreadystatechange=function(){
        if(xhr.readyState===4&&xhr.status===200){
            document.querySelector('.clock').innerHTML=xhr.responseText
            longConnect()
        }
    }
    xhr.send()
}
longConnect()
           
  • 可以發現用戶端請求就是個遞歸,服務端開定時器隻是簡單的政策,比如有資料更新了,可以通過回調函數把目前所有在服務端長輪詢的請求提前傳回,并不一定要開定時器。但請求仍非常多的堆積在服務端,是以這個缺點是可能對服務端壓力比較大,另外我試了下ie無效,用戶端會卡死,不知道是我ie問題還是本來就不行。

三、iframe流

  • 這個技術可以說非常的騷,以前還沒有ajax的時候,iframe就用來代替ajax的活,現在還能做實時更新。
  • 原理是這樣:利用iframe的src加載的特性來擷取資料,這個iframe的src本質和頁面的script的src差不多,會阻塞渲染,如果不想阻塞可以異步加載iframe,是以這個操作會讓浏覽器上方有個圈一直轉。但是谷歌工程師比較牛逼,開發的ActiveX的

    htmlfile

    插件可以不讓圈轉了,谷歌用這技術用到了gmail與gtalk裡。插件就不說了,一般通過object标簽來插入。下面看代碼:

服務端

let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.get('/clock',(req,res)=>{
    setInterval(() => {
        res.write(`<script type="text/javascript">
        parent.document.querySelector('#clock').innerHTML = "${new Date().toLocaleTimeString()}"
        </script>
    `)
    }, 1000);
})
app.listen(8080)
           

用戶端

<div id="clock" style="height: 100px;"></div>
    <iframe src="http://localhost:8080/clock" style="display: none;"/>
    <div>5</div>
           
  • 可以試一下用戶端這個5是出不來的,因為被iframe給阻塞了。
  • 是以這個可以改造成異步加載個iframe,然後服務端再把資料寫給用戶端,有人會說這個不是有點像jsonp嗎?這玩意确實跟jsonp很像,但是我拿script标簽替換iframe标簽發現無效,用戶端收不到服務端寫入的資料,雖然network裡連接配接沒有結束。是以必須隻能拿iframe标簽才行。
  • 這個方法有個很大的優點,由于iframe标簽早就有了是以,就是ie可用。。
  • 另外還需要注意http1.1中不能使用超過2個以上長連接配接,否則會阻塞别的http請求,如果一個頁面有多個地方需要這麼搞,建議統一用一個長連接配接傳回資料。

SSE

  • SSE是

    server-sent-event

    的縮寫,H5提供了個接口叫

    EventSource

    ,這個接口ie當然沒有實作,我看了下mdn,edge也沒實作。其他浏覽器都支援,chorme浏覽器在chorme6開始支援。這個接口可以允許用戶端去監聽服務端推送的事件。
  • 代碼:

服務端

let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.get('/clock',(req,res)=>{
    res.setHeader('Content-Type','text/event-stream')
    setInterval(() => {
        res.write(`event:xxxx\ndata:${new Date().toLocaleTimeString()}\n\n`)
    }, 1000);
})
app.listen(8080)
           

用戶端

<body>
    <div>1</div>
    <div id="clock" ></div>
</body>
<script>
var eventSource = new EventSource('/clock');
eventSource.addEventListener('open',function(){
    console.log('connect');
})
eventSource.addEventListener('xxxx', function(e){
    console.log(e);
    document.getElementById('clock').innerHTML=e.data
})
eventSource.addEventListener('error' ,function(err){
    console.log(err);
})
</script>
           
  • 還有2個地方需要注意下,一個是服務端傳回格式必須要

    text/event-stream

    不然用戶端報錯。另外就是寫入資料時注意不能有空格,資料發完需要兩下

    \n

    ,否則就不認。
  • 這裡事件名就是xxxx,對應服務端

    event:xxxx

    字元串。
  • 此時打開控制台可以發現原來的preview按鈕換成了EventStream按鈕,可以看見服務端推送消息。
  • 這個東西優點就是友善快捷好用,不需要額外裝東西,還可以配置自動重連什麼的。
  • 由于寫字元串比較麻煩,nodejs裡也有一些人做了些sse包,我搜了一下大同小異,就是把字元串變成可以做個對象,然後隻要傳對象交給它處理就行了。例子:
const SseStream = require('ssestream')
 
function (req, res) {
  const sse = new SseStream(req)
  sse.pipe(res)
  
  const message = {
    data: 'hello\nworld',
    retry:2000
  }
  sse.write(message)
}
           

繼續閱讀