Server-Sent Events 教程

阮一峰發表於2017-05-27

伺服器向瀏覽器推送資訊,除了 WebSocket,還有一種方法:Server-Sent Events(以下簡稱 SSE)。本文介紹它的用法。

一、SSE 的本質

嚴格地說,HTTP 協議無法做到伺服器主動推送資訊。但是,有一種變通方法,就是伺服器向客戶端宣告,接下來要傳送的是流資訊(streaming)。

也就是說,傳送的不是一次性的資料包,而是一個資料流,會連續不斷地傳送過來。這時,客戶端不會關閉連線,會一直等著伺服器發過來的新的資料流,視訊播放就是這樣的例子。本質上,這種通訊就是以流資訊的方式,完成一次用時很長的下載。

SSE 就是利用這種機制,使用流資訊向瀏覽器推送資訊。它基於 HTTP 協議,目前除了 IE/Edge,其他瀏覽器都支援。

二、SSE 的特點

SSE 與 WebSocket 作用相似,都是建立瀏覽器與伺服器之間的通訊渠道,然後伺服器向瀏覽器推送資訊。

總體來說,WebSocket 更強大和靈活。因為它是全雙工通道,可以雙向通訊;SSE 是單向通道,只能伺服器向瀏覽器傳送,因為流資訊本質上就是下載。如果瀏覽器向伺服器傳送資訊,就變成了另一次 HTTP 請求。

但是,SSE 也有自己的優點。

  • SSE 使用 HTTP 協議,現有的伺服器軟體都支援。WebSocket 是一個獨立協議。
  • SSE 屬於輕量級,使用簡單;WebSocket 協議相對複雜。
  • SSE 預設支援斷線重連,WebSocket 需要自己實現。
  • SSE 一般只用來傳送文字,二進位制資料需要編碼後傳送,WebSocket 預設支援傳送二進位制資料。
  • SSE 支援自定義傳送的訊息型別。

因此,兩者各有特點,適合不同的場合。

三、客戶端 API

3.1 EventSource 物件

SSE 的客戶端 API 部署在EventSource物件上。下面的程式碼可以檢測瀏覽器是否支援 SSE。

使用 SSE 時,瀏覽器首先生成一個EventSource例項,向伺服器發起連線。

上面的url可以與當前網址同域,也可以跨域。跨域時,可以指定第二個引數,開啟withCredentials屬性,表示是否一起傳送 Cookie。

EventSource例項的readyState屬性,表明連線的當前狀態。該屬性只讀,可以取以下值。

  • 0:相當於常量EventSource.CONNECTING,表示連線還未建立,或者斷線正在重連。
  • 1:相當於常量EventSource.OPEN,表示連線已經建立,可以接受資料。
  • 2:相當於常量EventSource.CLOSED,表示連線已斷,且不會重連。

3.2 基本用法

連線一旦建立,就會觸發open事件,可以在onopen屬性定義回撥函式。

客戶端收到伺服器發來的資料,就會觸發message事件,可以在onmessage屬性的回撥函式。

上面程式碼中,事件物件的data屬性就是伺服器端傳回的資料(文字格式)。

如果發生通訊錯誤(比如連線中斷),就會觸發error事件,可以在onerror屬性定義回撥函式。

close方法用於關閉 SSE 連線。

3.3 自定義事件

預設情況下,伺服器發來的資料,總是觸發瀏覽器EventSource例項的message事件。開發者還可以自定義 SSE 事件,這種情況下,傳送回來的資料不會觸發message事件。

上面程式碼中,瀏覽器對 SSE 的foo事件進行監聽。如何實現伺服器傳送foo事件,請看下文。

四、伺服器實現

4.1 資料格式

伺服器向瀏覽器傳送的 SSE 資料,必須是 UTF-8 編碼的文字,具有如下的 HTTP 頭資訊。

上面三行之中,第一行的Content-Type必須指定 MIME 型別為event-steam。

每一次傳送的資訊,由若干個message組成,每個message之間用nn分隔。每個message內部由若干行組成,每一行都是如下格式。

上面的field可以取四個值。

  • data
  • event
  • id
  • retry

此外,還可以有冒號開頭的行,表示註釋。通常,伺服器每隔一段時間就會向瀏覽器傳送一個註釋,保持連線不中斷。

下面是一個例子。

4.2 data 欄位

資料內容用data欄位表示。

如果資料很長,可以分成多行,最後一行用nn結尾,前面行都用n結尾。

下面是一個傳送 JSON 資料的例子。

4.3 id 欄位

資料識別符號用id欄位表示,相當於每一條資料的編號。

瀏覽器用lastEventId屬性讀取這個值。一旦連線斷線,瀏覽器會傳送一個 HTTP 頭,裡面包含一個特殊的Last-Event-ID頭資訊,將這個值傳送回來,用來幫助伺服器端重建連線。因此,這個頭資訊可以被視為一種同步機制。

4.4 event 欄位

event欄位表示自定義的事件型別,預設是message事件。瀏覽器可以用addEventListener()監聽該事件。

上面的程式碼創造了三條資訊。第一條的名字是foo,觸發瀏覽器的foo事件;第二條未取名,表示預設型別,觸發瀏覽器的message事件;第三條是bar,觸發瀏覽器的bar事件。

下面是另一個例子。

4.5 retry 欄位

伺服器可以用retry欄位,指定瀏覽器重新發起連線的時間間隔。

兩種情況會導致瀏覽器重新發起連線:一種是時間間隔到期,二是由於網路錯誤等原因,導致連線出錯。

五、Node 伺服器例項

SSE 要求伺服器與瀏覽器保持連線。對於不同的伺服器軟體來說,所消耗的資源是不一樣的。Apache 伺服器,每個連線就是一個執行緒,如果要維持大量連線,勢必要消耗大量資源。Node 則是所有連線都使用同一個執行緒,因此消耗的資源會小得多,但是這要求每個連線不能包含很耗時的操作,比如磁碟的 IO 讀寫。

下面是 Node 的 SSE 伺服器例項

請將上面的程式碼儲存為server.js,然後執行下面的命令。

上面的命令會在本機的8844埠,開啟一個 HTTP 服務。

然後,開啟這個網頁,檢視客戶端程式碼並執行。

六、參考連結

(完)

相關文章