關於HTML5服務器發送事件(SSE)


最近在看 W3School 上關於 HTML 5 的教程。在看到 HTML 5 服務器發送事件 ( SSE, server-sent event ) 時,沒怎么弄明白示例代碼是怎么回事,尋找其他教程,大部分也只給出了類似的代碼,沒有解釋。花了一點時間,大概弄清楚了,這里記錄一下。

一段示例代碼

利用 SSE,網頁可以自動獲取來自服務器的更新。以前也可能做到這一點 ( 如 AJAX ),前提是網頁不得不詢問是否有可用的更新。通過服務器發送事件,更新能夠自動到達。簡單說就是把輪詢獲取更新的方式 ( pull ) ,改為接受服務器主動推送的方式 ( push )。

下面是 W3School 上的一段示例代碼,包含客戶端的 JavaScript ,以及服務端的 PHP 代碼:

var source = new EventSource("demo_sse.php");
source.onmessage = function(event) {
    document.getElementById("result").innerHTML += event.data + "<br />";
};

這段代碼不斷的從 demo_sse.php 獲取數據,並將得到的結果輸出到 id 為 result 的 div 中。

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

$time = date('r');
echo "data: The server time is: {$time}\n\n";
flush();
?>

這段代碼將服務器當前時間,回復給客戶端。最終,客戶端頁面上顯示出了如下結果:

The server time is: Fri, 29 Aug 2014 02:03:21 +0800
The server time is: Fri, 29 Aug 2014 02:03:24 +0800
The server time is: Fri, 29 Aug 2014 02:03:27 +0800
The server time is: Fri, 29 Aug 2014 02:03:30 +0800
The server time is: Fri, 29 Aug 2014 02:03:33 +0800
The server time is: Fri, 29 Aug 2014 02:03:36 +0800
The server time is: Fri, 29 Aug 2014 02:03:39 +0800
The server time is: Fri, 29 Aug 2014 02:03:42 +0800
The server time is: Fri, 29 Aug 2014 02:03:45 +0800
The server time is: Fri, 29 Aug 2014 02:03:48 +0800
...

頁面每3秒鍾顯示1次服務器時間。這一過程,具體是如何實現的?這里存在一個明顯的問題:對於 demo_sse.php 來說,每次相應客戶端的請求,服務端只會輸出1次系統當前時間,使用 flush() 方法清空輸出緩沖區,本次 HTTP 會話立刻結束。那頁面為何會顯示出一連串的時間戳呢?如果客戶端每過一個時間間隔,就向服務器發出一次請求,那么和之前的輪詢式 ( pull ) 更新方式有什么區別呢?

發生了什么

首先確認,這段 JavaScript 代碼都做了什么,用簡單的 HTTP 抓包程序,得到了下圖中的結果:

HTTP 抓包結果

左邊列表中,每一項代表了一次 HTTP 請求,可以看到,客戶端 (瀏覽器),每3秒鍾向 demo_sse.php 發出一次 GET 請求,且每次請求都一模一樣。客戶端確實是采取了一種類似於輪詢 ( pull ) 的方式獲取更新,這並不是我們想要的結果。

舉個例子,如果這個網頁需要顯示 Apple 公司的實時股價,我們更傾向於當股價有變化時,服務器主動將變化后的股價推送給客戶端,而不是客戶端每隔3秒詢問一次服務端,Apple 公司的股價有沒有變化 (在移動客戶端中,問題更嚴重,輪詢式的更新將極大增加用戶設備的電量消耗,縮短待機時間,並消耗用戶的流量)。

那么如何實現讓服務器主動推送呢。其實很簡單,只要讓服務器保持 HTTP 會話不關閉,當有新的更新時,立刻輸出,並 flush() 即可。下面的例子中,服務端保持 HTTP 會話不中斷,每隔10秒,發送一個遞增的數字。

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

$i = 0;
while (true) {
    echo "data: ". $i++ ."\n\n";
    flush();
    
    // 暫停10秒
    sleep(10);
}
?>

保持客戶端的代碼不變。客戶端頁面,每隔10秒鍾顯示一行遞增的數字。並且從抓包結果看,客戶端只對服務端發起了一次 HTTP 請求,從而實現了每當有新的信息,服務器主動推送到客戶端 ( push ),而非客戶端輪詢 ( pull )。

必須說明,這段代碼只是為了演示 SSE 如何工作,並沒有任何實用價值。使用 PHP 通過這樣的方式維持長時間的 HTTP 會話並不優雅。事實上,保持客戶端頁面一段時間后,HTTP 會話被服務器因超時中斷。這時,客戶端將在3秒后,再次發出 GET 請求,直到服務端再次中斷,等待3秒后再次發出請求……客戶端不斷循環這一過程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM