返回 导航

HTML5 / CSS3

hangge.com

HTML5 - 服务器发送事件使用详解(附样例)

作者:hangge | 2017-07-14 08:10
有时我们的网页需要与 Web 服务器保持长期的联系。比如股票页面,打开这个页面不用管它,页面上股票价格也会定期更新。又比如 Web 邮箱程序,打开后也会自动接收新邮件。
要实现上面的功能,一种方法是轮询。即使用 js setInterval() setTimeout() 函数,每隔一定时间就向 Web 服务器请求新数据。但轮询有时效率不高,而且为了知道是否有新数据,每次轮询都要向服务器发送请求,建立新连接。如果客户端多了,会给服务器造成很大压力。
本文介绍另一种方案:服务器发送事件。

一、服务器发送事件介绍

1,基本介绍

(1)服务器发送事件可以让网页与 Web 服务器保持连接。服务器任何时候都可以发送消息,而不必频繁断开连接,然后还要重新连接并重新运行服务器端脚本。
(2)当然服务器发送事件也是支持轮询的。
(3)服务器发送事件使用简单,稳定可靠,且大多数 Web 服务器都支持。

2,浏览器支持情况

(1)桌面浏览器
  • Chrome:完美支持
  • Firefox:完美支持
  • SafariSafari 5 起开始支持(2010年)
  • IEIE 完全不支持(包括后面的 Edge
  • OperaOpera 11.5 起开始支持(2011年)

(2)移动设备
  • iOS:完美支持
  • AndroidAndroid 4.4 起开始支持(2013年)

3,检测当前浏览器是否支持服务器发送事件

通过测试是否存在 window.EventSource 属性,可以判断出浏览器是否支持服务端事件。
if(typeof(EventSource)!=="undefined") {
   alert("当前浏览器支持服务器发送事件。");
}else{
   alert("当前浏览器不支持服务器发送事件。");
}

4,消息格式

(1)下面是一个最简单最标准的服务器发送事件消息数据格式。每条消息必须以 data: 开头,然后是实际的消息内容,最后加上换行符(PHP \n\n 表示换行符)
data:这是一条服务器消息。\n\n

(2)也可以把一条消息分成多行,每行都要跟一个行结束符(\n)。这样就可以发送较复杂的内容了。
注意:一条消息分成多行,每行开头仍要有 data:,而整个消息结束同样要跟 \n\n
data:这是一条服务器消息。\n
data:欢迎访问 hangge.com\n\n

利用这个格式,甚至可以发送 JSON 数据,这样网页只需要一步就可以把文本转为 JavaScript 对象。
data:{\n
data:"message": "hangge.com"\n
data:}\n\n

(3)除消息本身外,Web 服务器还可以发送唯一 ID 值(id: 前缀),和一个连接超时选项(retry: 前缀)
注意:我们的网页一般只需要关注消息本身,不用关心 ID 和连接超时信息。ID 和超时信息是给浏览器使用的,比如浏览器读到下面消息后会知道:
  • 如果连接已经断开,那么应该在 150000 毫秒后再重新建立连接。
  • 重新建立连接时,应该把 ID 编号 495 一起发送给服务器,以便确认。
id:123\n
retry:15000\n
data:这是一条服务器消息。\n\n
关于连接断开后重连
网络中断或者代理服务器等待数据超时等情况,都会造成浏览器与服务器失去联系。浏览器会在可能的情况下自动重新打开连接,默认等待时间为 3 秒。

(4)有时发送第一条消息和第二条消息之间要间隔很长一段时间,这个链接有可能会被某个代理服务器(位于 Web 服务器和客户机之间,用于分散流量的服务器)给切断。为了避免这种情况发送,可以每隔 15 秒左右,发送一条只包含冒号(:)而没有文本内容的注释消息。
:\n\n

二、服务器发送消息,页面处理消息

1,效果图

(1)点击“开始接收”按钮开始监听,页面会连续不断地收到服务器发送的消息(2 秒钟一条),并将收到的消息显示到页面上。
(2)点击“停止接收”按钮则停止监听服务器事件。
         

2,样例代码

(1)服务端代码(TimeEvents.php
<?
//服务端事件标准规定(将MIME类型设为text/event-stream)
header("Content-Type: text/event-stream");
//告诉Web服务器关闭Web缓存。(否则含有时间的消息可能不会按先后顺序到达)
header("Cache-Control: no-cache");
//关闭PHP内置的缓冲机制,这样PHP脚本返回的数据就会立即传给浏览器。
ob_end_clean();
//开始不间断的循环(只要客户端存在)
do {
  //取得当前时间
  $currentTime = date("H:i:s", time());

  //把时间放到消息中发送(PHP_EOL常量即为前面说的\n字符)
  echo "data:服务器当前时间:".$currentTime.PHP_EOL;
  echo PHP_EOL;

  //立即发送数据(而不是先缓冲起来,等到PHP代码执行完毕再发送)
  flush();

  //等两秒钟再继续下一次循环,创建新消息
  sleep(2);
} while(true);
?>

(2)页面代码(index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <script type="text/javascript">
    //用于显示消息的div容器
    var messageLog;
    //EventSource对象
    var source;

    //页面加载后,将用于显示消息的div存放到全局变量中
    window.onload = function() {
      messageLog = document.getElementById('messageLog');
    }

    //点击开始按钮,开始事件监听
    function startListening() {
      //创建新的EventSource对象
      source = new EventSource("TimeEvents.php");
      //指定onmessage事件的处理函数
      source.onmessage = receiveMessage;
      messageLog.innerHTML += "开始监听服务端事件...<br>";
    }

    //页面接收到消息时触发
    function receiveMessage(e) {
      //从事件对象的data属性中取得消息,并显示。
      //注意:网页收到的消息不会包含前缀data:和结束的\n\n符号,只有消息内容本身
      messageLog.innerHTML += e.data + "<br>";
    }

    //点击停止按钮,停止事件监听
    function stopListening() {
      //通过EventSource对象的close()方法,可以让也没停止监听服务器事件
      source.close();
      messageLog.innerHTML += "停止监听服务端事件!<br>";
    }

  </script>
  <body>
    <div id="controls">
      <button onclick="startListening()">开始接收</button>
      <button onclick="stopListening()">停止接收</button>
    </div>
    <div id="messageLog"></div>
  </body>
</html>

三 、轮询服务端事件

1,实现原理

(1)上面的样例是页面发送请求,连接保持打开,服务器定时发送消息。当连接发生中断时(比如网络问题),浏览器也会自动重新连接。
(2)而如果是服务器主动关闭了连接,那么网页仍然会自动重新打开连接(默认等待 3 秒钟),再次请求脚本,然后从头开始。
(3)假设我写了一个比较短的服务器脚本,只发送一条消息。那么此时网页就像在使用轮询,周期性地重新建立连接。唯一的差别就是 Web 服务器会告诉浏览器再等待多长时间才能检查新数据。而在真正使用轮询的网页中,等待时间是在 JavaScript 代码中确定的。
(4)利用前面提到的重连机制,可以实现一些特殊需求。比如股市信息页面,在股市交易期间正常更新数据。到了今天结束交易时,服务器这边就关闭连接,并建议浏览器再等待 17 个小时后再重新连接。这样既保证数据最新,又有助于减少 Web 服务器的流量。

2,样例代码

下面我们对服务端代码做个修改(html 页面代码不变)。这里混合了两种手段,它保持连接(并周期性地发送消息)1 分钟,并建议浏览器等待两分钟再重新连接,然后关闭连接。

<?
//服务端事件标准规定(将MIME类型设为text/event-stream)
header("Content-Type: text/event-stream");
//告诉Web服务器关闭Web缓存。(否则含有时间的消息可能不会按先后顺序到达)
header("Cache-Control: no-cache");
//关闭PHP内置的缓冲机制,这样PHP脚本返回的数据就会立即传给浏览器。
ob_end_clean();
//开始不间断的循环(只要客户端存在)

//告诉浏览器在连接关闭后,等待2分钟再重新连接
echo "retry:120000".PHP_EOL;

//保存开始时间
$startTime = time();

do {
  //取得当前时间
  $currentTime = date("H:i:s", time());

  //把时间放到消息中发送(PHP_EOL常量即为前面说的\n字符)
  echo "data:服务器当前时间:".$currentTime.PHP_EOL;
  echo PHP_EOL;

  //立即发送数据(而不是先缓冲起来,等到PHP代码执行完毕再发送)
  flush();

  //如果过了1分钟,则结束脚本
  if((time() - $startTime) > 60) {
    die();
  }

  //等10秒钟再继续下一次循环,创建新消息
  sleep(10);
} while(true);
?>

3,运行效果

可以看到脚本运行后会先用 1 分钟时间进行更新,然后暂停服务 2 分钟。接着再重连,继续用 1 分钟时间进行更新.....如此反复。
评论

全部评论(0)

回到顶部