HTML5 - 服务器发送事件使用详解(附样例)
作者:hangge | 2017-07-14 08:10
有时我们的网页需要与 Web 服务器保持长期的联系。比如股票页面,打开这个页面不用管它,页面上股票价格也会定期更新。又比如 Web 邮箱程序,打开后也会自动接收新邮件。
要实现上面的功能,一种方法是轮询。即使用 js 的 setInterval() 或 setTimeout() 函数,每隔一定时间就向 Web 服务器请求新数据。但轮询有时效率不高,而且为了知道是否有新数据,每次轮询都要向服务器发送请求,建立新连接。如果客户端多了,会给服务器造成很大压力。
本文介绍另一种方案:服务器发送事件。
一、服务器发送事件介绍
1,基本介绍
(1)服务器发送事件可以让网页与 Web 服务器保持连接。服务器任何时候都可以发送消息,而不必频繁断开连接,然后还要重新连接并重新运行服务器端脚本。
(2)当然服务器发送事件也是支持轮询的。
(3)服务器发送事件使用简单,稳定可靠,且大多数 Web 服务器都支持。
2,浏览器支持情况
(1)桌面浏览器
- Chrome:完美支持
- Firefox:完美支持
- Safari:Safari 5 起开始支持(2010年)
- IE:IE 完全不支持(包括后面的 Edge)
- Opera:Opera 11.5 起开始支持(2011年)
(2)移动设备
- iOS:完美支持
- Android:Android 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 秒。
网络中断或者代理服务器等待数据超时等情况,都会造成浏览器与服务器失去联系。浏览器会在可能的情况下自动重新打开连接,默认等待时间为 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)