31

PHP+SSE服务器向客户端推送消息

 5 years ago
source link: https://www.helloweba.net/javascript/606.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

SSE与WebSocket作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。 但是WebSocket比SSE强大很多,SSE只能作为一个轻量级的消息推送方案,解决了从服务端向客户端单向推送消息的场景,而Websocket是全双工通道,可以双向通信。 SSE应用场景可以是微博更新、股价更新、消息通知、赛事结果等。

目前主流浏览器都支持SSE,但是IE系除外。

客户端代码

先来看客户端代码,新建一个html页面文件,在script部分添加以下代码:

if(typeof(EventSource) !== "undefined") {
    let source = new EventSource("sse.php");
    source.onmessage = (e) => {
        if (e.data == 'null') {
            return false;
        } else {
            let edata = JSON.parse(e.data);
            $('#result').append(edata.id + ':' + edata.message + "<br/>");
        }
    };
} else {
    alert('您的浏览器不支持SSE');
}

首先,使用 typeof(EventSource) 来判断浏览器对SSE的支持情况。

接着创建一个新的 EventSource 对象,然后定义发送更新的服务端的 URL(本例中是 "sse.php"),如果是跨域的请求,需要这样设置: let source = new EventSource("http://xxx.com/sse.php", { withCredentials: true }); ,并需要服务端代码开启允许跨域。

每接收到一次更新,就会发生 onmessage 事件。

当 onmessage 事件发生时,把已接收的数据推入 id 为 #result 的元素中。

EventSource 对象支持3种事件:

onopen :当通往服务器的连接被打开时触发。

onmessage :当接收到消息时触发。

onerror :当发生错误时触发。

出于安全,我们可以在 onmessage 事件中检测消息的来源域:

source.onmessage = (e) => {
  if (e.origin != 'https://www.helloweba.net') {
    alert('消息来源不属于https://www.helloweba.net');
    return;
  }
  ...
}

服务端代码

我们使用PHP来写一个服务端发送数据的例子,当然你也可以使用Java/Python等任意服务端语言实现。

服务器端事件流的语法是非常简单的。把 "Content-Type" 报头设置为 "text/event-stream"。现在,就可以开始发送事件流了。

header('X-Accel-Buffering: no');
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
ob_end_clean();
ob_implicit_flush(1);

while(1){

    $data =  [
        "id" => time(),
        "message" => '欢迎来到helloweba,现在是北京时间'.date('Y-m-d H:i:s')
    ];
    
    returnEventData($data);
    sleep(10);
}

function returnEventData($returnData, $event='message', $id=0, $retry=0){

    $str = '';
    if($id>0){
        $str .= "id: {$id}".PHP_EOL;
    }
    if($event){
        $str.= "event: {$event}".PHP_EOL;
    }
    if($retry>0){
        $str .= "retry: {$retry}".PHP_EOL;
    }
    if(is_array($returnData)){
        $returnData = json_encode($returnData);
    }
    $str .= "data: {$returnData}".PHP_EOL;
    $str .= PHP_EOL;
    echo $str;
}

以上代码流程大致为:

1.把报头 "Content-Type" 设置为 "text/event-stream"

2.规定不对页面进行缓存;

3.输出发送数据;

4.向客户端刷新输出数据。

注意:每一次发送的信息,由若干个 message 组成,每个message内部由若干行组成,每一行都是如下格式。

[field]: value\n

其中 [field] 有四个值,分别是:

id :数据标识符用id字段表示,相当于每一条数据的编号。

event :表示自定义的事件类型,默认是 message 事件。浏览器可以用 addEventListener() 监听该事件。

retry :指定浏览器重新发起连接的时间间隔。当时间间隔到期会重连,另外一个是由于网络错误等原因,导致连接出错时也会重连。

data :数据内容,如果数据很长,可以分成多行,最后一行用 \n\n 结尾,前面行都用 \n 结尾。

完整的消息内容格式:

id: msg1\n
event: foo\n
retry: 10000\n
data: some text\n
data: another message\n
data: with two lines \n\n

上述代码中,我们设置了每隔10秒钟向客户端输出一条数据,实际应用中服务端有个任务当发现新的数据时就触发输出流事件。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK