HTML5 Server-Sent Events(伺服器發送事件) 教學範例 for PHP - MIS 腳印
MIS 腳印 logo

MIS 腳印

記錄 IT 學習的軌跡

HTML5 Server-Sent Events(伺服器發送事件) 教學範例 for PHP

透過 HTML5 Web API Server-Sent Events(伺服器發送事件) 搭配 PHP 來實作 Server 主動單向發送訊息給 Client Browser 的詳細教學與程式實作範例。

JavaScript
線上範例 下載範例

EventSource – Client

建立物件

判斷 Browser 有支援在建立 EventSource 物件,並指定 Server 檔案路徑:

if (typeof(EventSource) !== 'undefined') {
    var sse = new EventSource('server.php');
} else {
    alert('瀏覽器不支援!');
}

事件

添加 EventSource 物件會用到的三種事件,說明如下表:

sse.addEventListener('open', open, false);
sse.addEventListener('message', message, false);
sse.addEventListener('error', error, false);

事件表

事件處理程序 事件處理程序事件類型 說明
onopen open 與 Server 成功建立連結
onmessage message 監聽 Server 傳來的訊息,當接收到連續兩個換行 \n\n
onerror error 與 Server 連結發生錯誤

PHP – Server

程式運作時間

Server-Sent Events 與 Server 連結後會持續的運作程式,而 PHP 預設運作的時間為 30 秒,因此必須修改為 0 不限制。

新增於程式碼開頭:

ini_set('max_execution_time', 0);

or 修改 php.ini 檔(須重啟服務):

max_execution_time=0

表頭

Server 程式要能夠發送事件流必須將 Content-Type 表頭設置為 text/event-stream:

header('Content-Type: text/event-stream');

表頭必須設置禁止瀏覽器快取網頁(不緩存內容):

header('Cache-Control: no-cache');

Nginx 設定

針對 Nginx 立即輸出緩衝區資料:

header('X-Accel-Buffering: no');

伺服器緩衝

預設情況是當程式結束後才會將資料一次送出(輸出)至 Browser,但使用 Server-Sent Events 應立即輸出每次的資料,因此必須設定緩衝讓資料立即輸出。

首先將緩衝區資料輸出給 Server,但並不是輸出到螢幕上:

ob_flush();

在將 Server 上準備輸出的資料輸出至 Browser 顯示出來:

flush();

事件流格式

限制條件:

  • 程式必須指定為 UTF-8 格式編碼
  • 訊息內不能有空格、換行

一個字段的組成:

  • 名稱
  • 冒號
  • 空格
"id: D121xxxxxx"

設置多個字段,使用 \n 界定符號:

"id: D121xxxxxx\ndata: {name: '王小傑', date: '2017-01-01'}"

使用連續兩個換行 \n\n 來送出一則訊息。當 Server 送出連續兩個換行 \n\n,EventSource 就會把它當成一個事件(預設自動觸發 onmessage 事件):

"\n\n"

字段表

字段名稱 說明
event 自行指定 Client 觸發的 event 名稱,如未指定預設為 onmessage
data 如果包含多個資料可使用 \n 界定符號,Client 會用界定符號將它們連接成一個字串
id 事件 ID,會成為目前 EventSource 的內部屬性
retry 整數值,指定重新連結的時間(毫秒)。非整數值會被忽略

程式實作範例

Client

<!DOCTYPE html>
<html lang="zh-Hant-TW">
<head>

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>HTML5 API Server-Send Event(伺服器發送事件)</title>

<script type="text/javascript">
window.onload = function() {
    document.querySelector('button').addEventListener('click', click, false);

    if (typeof(EventSource) !== 'undefined') {
        // 指定 Server 檔案路徑
        var sse = new EventSource('server.php');

        sse.addEventListener('open', open, false);
        sse.addEventListener('message', message, false);
        sse.addEventListener('error', error, false);
    } else {
        alert('瀏覽器不支援!');
    }

    function click(event) {
        closeEventSource();
    }

    function open(event) {
        console.log('與 Server 正常連接!');
    }

    function message(event) {
        var pullData = JSON.parse(event.data);
        var newElement = document.createElement('li');
        newElement.innerHTML = pullData.name + ', ' + pullData.date;
        document.body.appendChild(newElement);
    }

    function error(event) {
        closeEventSource();

        alert('連接發生錯誤!');
    };

    function closeEventSource() {
        sse.close();

        alert('已中斷 Server 連線!');
    }
}
</script>
</head>
<body>

<button>中斷 Server 連線</button>

</body>
</html>

Server

<!DOCTYPE html>
<?php
ini_set('max_execution_time', 0);

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');    // 針對 Nginx 立即輸出緩衝區資料


// 讓迴圈無限執行
while (true) {
    // 傳送的資料
    $data = array(
        'name' => '王小傑',
        'date' => date('Y-m-d H:i:s')
    );

    // 將資料編碼 json 傳送
    echo "id: D121xxxxxx\ndata: " . json_encode($data);
    echo "\n\n";

    ob_flush();
    flush();

    // 控制睡眠多久再執行(秒)
    sleep(1);
}

執行結果

  • 左側為 EventSource 連結的 Server 檔案
  • 右側目前已收接了五則訊息,而接收的字段有 id(字串格式)與 data(json 格式)
  • Browser 顯示的資訊則是從字段 data 的 json 格式,處理後添加 DOM 呈現的
  • 要中斷 EventSource 連結,可點擊「中斷 Server 連線」按鈕

參考

在〈HTML5 Server-Sent Events(伺服器發送事件) 教學範例 for PHP〉中有 1 則留言

發表迴響