HTML 5 服务器发送事件

来源:互联网 发布:2017网络流行语翻译 编辑:程序博客网 时间:2024/06/07 02:56

对于一般的 Web 应用开发,大多数开发人员并不陌生。在 Web 应用中,浏览器和服务器之间使用的是请求 / 响应的交互模式。浏览器发出请求,服务器根据收到的请求来生成相应的响应。浏览器再对收到的响应进行处理,展现给用户。响应的格式可能是 HTML、XML 或 JSON 等。随着 REST 架构风格和 AJAX 的流行,服务器更多地使用 JSON 作为响应的数据格式。Web 应用使用 XMLHttpRequest 对象来发送请求,并根据服务器端返回的数据,对页面的内容进行动态更新。通常来说,用户在页面上的操作,比如点击或移动鼠标,会触发相应的事件。由 XMLHttpRequest 对象来发出请求,得到服务器响应之后进行页面的局部更新。这种方式的不足之处在于:服务器端产生的数据变化不能及时地通知浏览器,而是需要等到下次请求发出时才能被浏览器获取。对于某些对数据实时性要求很高的应用来说,这种延迟是不能接受的。


为了满足这类应用的需求,就需要有某种方式能够从服务器端推送数据给浏览器,以保证服务器端的数据变化可以在第一时间通知给用户。

目前常见的解决办法有不少,主要可以分成两类。这两类方法的区别在于是否基于 HTTP 协议来实现。不使用 HTTP 协议的做法是使用 HTML 5 新增的 WebSocket 规范,而使用 HTTP 协议的做法则包括简易轮询、COMET 技术和本文中要介绍的 HTML 5 服务器推送事件。


在介绍 HTML 5 服务器推送事件之前,首先介绍一些上面提到的几种服务器端数据推送技术。第一种是 WebSocket。WebSocket 规范是 HTML 5 中的一个重要组成部分,已经被很多主流浏览器所支持,也有不少基于 WebSocket 开发的应用。正如名称所表示的一样,WebSocket 使用的是套接字连接,基于 TCP 协议。使用 WebSocket 之后,实际上在服务器端和浏览器之间建立一个套接字连接,可以进行双向的数据传输。WebSocket 的功能是很强大的,使用起来也灵活,可以适用于不同的场景。不过 WebSocket 技术也比较复杂,包括服务器端和浏览器端的实现都不同于一般的 Web 应用。

除了 WebSocket 之外,其他的实现方式是基于 HTTP 协议来达到实时推送的效果。第一种做法是简易轮询,即浏览器端定时向服务器端发出请求,来查询是否有数据更新。这种做法比较简单,可以在一定程度上解决问题。不过对于轮询的时间间隔需要进行仔细考虑。轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担。

COMET 技术改进了简易轮询的缺点,使用的是长轮询。长轮询的方式在每次请求时,服务器端会保持该连接在一段时间内处于打开状态,而不是在响应完成之后就立即关闭。这样做的好处是在连接处于打开状态的时间段内,服务器端产生的数据更新可以被及时地返回给浏览器。当上一个长连接关闭之后,浏览器会立即打开一个新的长连接来继续请求。不过 COMET 技术的实现在服务器端和浏览器端都需要第三方库的支持。

综合比较上面提到的 4 种不同的技术,简易轮询由于其本身的缺陷,并不推荐使用。COMET 技术并不是 HTML 5 标准的一部分,从兼容标准的角度出发,也不推荐使用。WebSocket 规范和服务器推送技术都是 HTML 5 标准的组成部分,在主流浏览器上都提供了原生的支持,是推荐使用的。不过 WebSocket 规范更加复杂一些,适用于需要进行复杂双向数据通讯的场景。对于简单的服务器数据推送的场景,使用服务器推送事件就足够了。


在浏览器支持方面,服务器推送事件已经在除 IE 外的大部分桌面和移动浏览器上得到了支持


自定义的事件不好使啊,所以就不列了

以下是java 的例子:

运行的结果

2017-01-06 15:18:25
name=i like;count=200
2017-01-06 15:18:36
name=i like;count=200
2017-01-06 15:18:46
name=i like;count=200

每隔10秒打印一份


js文件

if (!!window.EventSource) {
    source = new EventSource('url地址');
    source.onmessage = function (e) {
        var d = e.data;
        try{
            var json = JSON.parse(d);
            console.log("name="+json.name+";count="+json.count);
        }catch(e){
            console.log(d);
        }
    }
}


java代码:

@RequestMapping(value = "tongzCount")
    public void statisticTongzCount(HttpServletResponse response,HttpServletRequest request) throws Exception {


        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        OutputStream bos = null;
        int count = 10;
        Map m = new HashMap();
        m.put("count",200);
        m.put("name","i like");
        try {
            String result = "data:"+sdf.format(new Date())+"\n\n";
            String result2 = "data:"+JsonUtility.toJSONString(m)+"\n\n";
            //声明浏览器在连接断开之后进行再次连接之前的等待时间 10秒
            String retry = "retry:"+10000+"\n\n";
            //事件的标识符
            String id="id:100\n\n";
   String last = request.getHeader("Last-Event-ID");
            bos = new BufferedOutputStream(response.getOutputStream());
            response.setContentType("text/event-stream");
            bos.write(result.getBytes());
            bos.write(result2.getBytes());
            bos.write(retry.getBytes());
            bos.write(id.getBytes());
            bos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


清单 1. 服务器端响应的示例
data: first eventdata: second eventid: 100event: myeventdata: third eventid: 101: this is a commentdata: fourth eventdata: fourth event continue


每个事件之间通过空行来分隔。对于每一行来说,冒号(“:”)前面表示的是该行的类型,冒号后面则是对应的值。

  • 类型为空白,表示该行是注释,会在处理时被忽略。
  • 类型为 data,表示该行包含的是数据。以 data 开头的行可以出现多次。所有这些行都是该事件的数据。
  • 类型为 event,表示该行用来声明事件的类型。浏览器在收到数据时,会产生对应类型的事件。
  • 类型为 id,表示该行用来声明事件的标识符。
  • 类型为 retry,表示该行用来声明浏览器在连接断开之后进行再次连接之前的等待时间。
在代码清单 1 中,第一个事件只包含数据“first event”,会产生默认的事件;第二个事件的标识符是 100,数据为“second event”;第三个事件会产生类型为“myevent”的事件;最后一个事件的数据为“fourth event\nfourth event continue”。当有多行数据时,实际的数据由每行数据以换行符连接而成。


如果服务器端返回的数据中包含了事件的标识符,浏览器会记录最近一次接收到的事件的标识符。如果与服务器端的连接中断,当浏览器端再次进行连接时,会通过 HTTP 头“Last-Event-ID”来声明最后一次接收到的事件的标识符。服务器端可以通过浏览器端发送的事件标识符来确定从哪个事件开始来继续连接。


对于服务器端返回的响应,浏览器端需要在 JavaScript 中使用 EventSource 对象来进行处理。EventSource 使用的是标准的事件监听器方式,只需要在对象上添加相应的事件处理方法即可。EventSource 提供了三个标准事件,如表 1 所示。

表 1. EventSource 对象提供的标准事件
名称说明事件处理方法open当成功与服务器建立连接时产生onopenmessage当收到服务器发送的事件时产生onmessageerror当出现错误时产生onerror

如之前所述,服务器端可以返回自定义类型的事件。对于这些事件,可以使用 addEventListener 方法来添加相应的事件处理方法。代码清单 2 给出了 EventSource 对象的使用示例。

清单 2. EventSource 对象的使用示例
var es = new EventSource('events');es.onmessage = function(e) {    console.log(e.data);};es.addEventListener('myevent', function(e) {    console.log(e.data);});

如代码清单 2 所示,在指定 URL 创建出 EventSource 对象之后,可以通过 onmessage 和 addEventListener 方法来添加事件处理方法。当服务器端有新的事件产生,相应的事件处理方法会被调用。EventSource 对象的 onmessage 属性的作用类似于 addEventListener( ‘ message ’ ),不过 onmessage 属性只支持一个事件处理方法。



0 0
原创粉丝点击