HTML5 Server-Sent Events(伺服器發送事件) 教學範例 for PHP
透過 HTML5 Web API Server-Sent Events(伺服器發送事件) 搭配 PHP 來實作 Server 主動單向發送訊息給 Client Browser 的詳細教學與程式實作範例。
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 應立即輸出每次的資料,因此必須設定緩衝讓資料立即輸出。
ob_flush()
與 flush()
有先後順序首先將緩衝區資料輸出給 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 連線」按鈕
線上
下載
參考
本著作係採用創用 CC 姓名標示-相同方式分享 3.0 台灣 授權條款授權.
非常有用的!
奇怪,同样代码,我设置半天不生效,一直是页面100%loading 才一次显示,是还有什么别的需要配置的吗?THX~