基於 HTTP 的實時 Web 通信

基於 HTTP 的實時 Web 通信

Web 應用的信息交互過程通常是客戶端通過瀏覽器發出一個請求,服務器端接收和審覈完請求後進行處理並返回結果給客戶端,然後客戶端瀏覽器將信息呈現出來,這種機制對於信息變化不是特別頻繁的應用是能夠滿足的,但是對於那些實時要求比較高的應用來說,當客戶端瀏覽器準備呈現這些信息的時候,這些信息在服務器端可能已經過時了。所以保持客戶端和服務器端的信息同步是實時 Web 應用的關鍵要素,本文將簡要介紹三種基於 HTTP 的實時 Web 通信方案。

短輪詢 (Short Polling)

短輪詢是最早的一種實現實時 Web 應用的方案。客戶端以一定的時間間隔向服務端發出請求,以頻繁請求的方式來保持客戶端和服務器端的同步。這種同步方案的最大問題是,當客戶端以固定頻率向服務器發起請求的時候,服務器端的數據可能並沒有更新,這樣會帶來很多無用的網絡傳輸,所以這是一種非常低效的實時方案。

短輪詢的優點:

  • 客戶端和服務器端實現都比較簡單

短輪詢的缺點:

  • 請求間隔小,則頻繁請求,無用請求多,浪費網絡帶寬和服務器資源
  • 請求間隔大,則信息時效性一般

短輪詢

客戶端短輪詢示例:

function poll() {
  axios.get('/api/shortPolling')
    .then(response => {
      // do something ...
      // 3秒後再發請求
      setTimeout(() => {
        poll();
      }, 3000);
    })
    .catch(error => {
      // error
    });
}

長輪詢 (Long Polling)

長輪詢是對短輪詢的改進和提高,目地是爲了降低無效的網絡傳輸。當服務器端沒有數據更新的時候,連接會保持一段時間直到數據或狀態改變或者時間過期,通過這種機制來減少無效的客戶端和服務器間的交互。當然,如果服務端的數據變更非常頻繁的話,這種機制和短輪詢比較起來沒有本質上的性能的提高。

長輪詢的優點:

  • 有數據更新時,能比較及時的響應
  • 沒有新數據更新時,服務端會保持連接直到超時,請求相對少,節省網絡帶寬

長輪詢的缺點:

  • 服務器端需要保持 HTTP 連接,消耗一定的資源
  • 服務器端數據變更頻繁時,請求也會變得頻繁

長輪詢

客戶端長輪詢示例:

function poll() {
  axios.get('/api/longPolling')
    .then(response => {
      // do something ...
      poll();
    })
    .catch(error => {
      // error
    });
}

服務器發送事件 (Server-Sent Events)

服務器發送事件(SSE)是客戶端通過 HTTP 連接自動從服務器接收更新的技術。

服務器端要按照規定的格式返回響應內容,響應的內容類型爲“text/event-stream”。響應文本的內容可以看成是一個事件流,由不同的事件所組成。每個事件由事件類型(event)和數據(data)兩部分組成,同時每個事件可以有一個可選的標識符(id)。不同事件的內容之間通過一個空行來分隔,每個事件的數據可能由多行組成。客戶端不再是使用瀏覽器提供的 XMLHttpRequest 對象來實現,而是使用 EventSource 對象實現,通過該對象建立連接和接收處理服務器推送的事件。

SSE 的優點:

  • 實時性較好
  • 除了超時的情況,客戶端一般只要發一次請求

SSE 的缺點:

  • 服務器端需要保持 HTTP 連接,消耗一定的資源
  • IE 和 Edge 瀏覽器不支持

服務器發送事件

客戶端創建一個連接到服務器接收事件,示例如下:

// 實例化 EventSource 對象,並指定一個 URL 地址
const eventSource = new EventSource('/api/serverSentEvents');
// 使用 addEventListener() 方法監聽事件
eventSource.addEventListener('message', (e) => {
  // 消息數據
  const data = e.data;
  // do something ... 
}, false);

事件流格式

事件流僅僅是一個簡單的文本數據流,每條消息後面都由一個空行作爲分隔符,以冒號開頭的行是註釋行。服務器可以定期發送一條註釋行消息,以保持連接防止連接超時。

: 這是註釋行

data: 一條沒有命名事件的消息數據

data: 一條多行
data: 消息數據

: 下面是事件名,瀏覽器在接收時,會產生對應事件名的事件
event: update
data: {"name": "harbor", "comment": "這是 JSON 格式的消息"}

除了上面看到的 eventdata 字段,還有 idretry 字段。

如果服務器設置了 id ,客戶端會保持最近的事件 ID。如果連接中斷了,重新連接發起新的請求時,會把最近接收到的事件 ID 放入 “Last-Event-ID” 請求的首部中,從而告知服務器最近的事件 ID。

: 下面是事件 ID
id: 8888
data: 一條消息數據

默認情況下,客戶端會在連接斷開後3秒嘗試進行重新連接。這個時間也可以由服務器來指定,以毫秒爲單位。

: 指定重新連接時間,單位毫秒
retry: 6000

Node.js 服務器端的例子

服務器端使用 Node.js 實現 SSE 的簡單示例:

const http = require('http');

http.createServer((request, response) => {
  const filePath = request.url;
  if (filePath === '/api/serverSentEvents') {
    // Content-Type 首部指定爲"text/event-stream"
    response.writeHead(200, {
      'Content-Type': "text/event-stream",
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive'
    });
    // 簡單模擬服務器檢查更新,每隔5秒發送一條消息
    setInterval(() => {
      const data = { timeStamp: Date.now() }; 
      // 返回一條簡單的消息,JSON 格式
      response.write(`data: ${JSON.stringify(data)}\n\n`);
    }, 5000);
  } else {
    response.writeHead(404);
    response.end();
  }
}).listen(8080);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章