美文网首页SpringBoot云课堂
SSE(服务器推送事件)的介绍、问题及解决

SSE(服务器推送事件)的介绍、问题及解决

作者: seymour1996 | 来源:发表于2018-01-13 22:16 被阅读675次

    介绍

    所谓SSE(Sever-Sent Event),就是浏览器向服务器发送一个HTTP请求,保持长连接,服务器不断单向地向浏览器推送“信息”(message),这么做是为了节约网络资源,不用一直发请求,建立新连接。

    • 长轮询:客户端向服务器发送Ajax请求,服务器接到请求后保持连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

      长轮询
    • 长连接:保持长时间的连接,服务器发送数据后,连接不关闭,下次有新数据时仍然用此连接发送

      长连接

    应用场景

    • 实时更新数据:比如网页上股票价格的更新,假设每秒钟要更新一次,如果用轮询的方法那我们每秒钟就要发送一次http请求,而用SSE,客户端发了一次请求以后,服务器就不断推送消息,客户端只需接收就可以了。
    • 新消息推送:比如有新的通知、新的订单就能第一时间推送给客户端

    评价

    • 优点:SSE和WebSocket相比,最大的优势是便利,服务端不需要其他的类库,开发难度较低,SSE和轮询相比它不用处理很多请求,不用每次建立新连接,延迟较低。
    • 缺点:如果客户端有很多,那就要保持很多长连接,这会占用服务器大量内存和连接数

    我们先来看一下《颠覆者》中的例子:
    public @ResponseBody String push(){
        Random r = new Random();
           try {
    Thread.sleep(5000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }   
    return "data:Testing 1,2,3" + r.nextInt() +"\n\n";
    }
    

    问题1:长连接

    这里return之后连接不就断了吗?怎么推送消息?
    测试结果:

    连接不停地打开关闭 多个请求
    问题解决:
    我们可以看到每个请求只返回了一次数据,服务器每次发完数据就断开了连接,但SSE默认会自动重连,所以客户端不断地重连(重新发请求),但这样不是类似ajax长轮询吗?作者可能对SSE存在误解了,这里有点大材小用。

    改进方案:
    我们发送消息时不用return,因为一旦return这个连接就断开了,我们应该改把数据写到response里,并用flush()方法输出。

    public void push(HttpServletResponse response) {
    response.setContentType("text/event-stream");
    response.setCharacterEncoding("utf-8");
        while (true) {
            Random r = new Random();
            try {
    Thread.sleep(1000);
    PrintWriter pw=response.getWriter();
    pw.write("data:Testing 1,2,3" + r.nextInt() + "\n\n");
    pw.flush();
    } catch (Exception e) {
          e.printStackTrace();
      }
    }
    

    实验结果:

    保持长连接 只有一次请求

    这样就实现了客户端只发送一次请求,服务器不断地单向推送消息,保持长连接。


    问题2:如何跳出循环

    我们写了while(true)让它保持连接,不停地推送数据,但是浏览器关闭时却没有抛出异常,这里很奇怪为什么连接关闭了还能执行flush()方法?这样的话浏览器关闭了,服务器却还在执行这个循环,这种情况显然是不允许的。

    问题解决:
    查阅了PrinterWriter源码后发现在flush()方法中已经对异常做了处理,所以调用时不会抛出异常,我们要检测异常可以调用它的checkError()函数

     public void flush() {
           try {
               synchronized (lock) {
                   ensureOpen();
                   out.flush();
               }
           }
           catch (IOException x) {
               trouble = true;
           }
       }
    

    我们关闭浏览器后,服务器下一次要推送时就会抛出异常,这个异常已经在PrintWriter的flush()中被捕捉了,我们只需它的调用checkError(),有错误的话return即可停止执行。

     if(pw.checkError()){
           log.println("客户端断开连接");
           return;
      }
    

    实验结果:

    断开连接
    可以看到浏览器关闭了以后,服务器也跳出了这个循环

    如果客户端想在不关闭页面的情况下主动关闭连接,只需要设置一个按钮,点击以后调用EventSource.colse()即可关闭连接,服务端推送数据时因为连接已经关闭,就会抛出异常,跳出循环。


    服务器推送数据 客户端点击按钮后
    客户端断开连接

    可以看到我们点击按钮调用EventSource.close()后客户端断开连接,服务端也停止推送了。


    问题3:兼容性

    SSE并没有在微软的IE和Edge浏览器上实现,怎么办?
    问题解决:
    用第三方javascript库实现,在页面引入evensource.js就可以实现

    下载地址:
    https://github.com/EventSource/eventsource

    实验结果:

    IE
    Edge
    可以看到引入js文件后,IE和Edge都能支持SSE了

    相关文章

      网友评论

      本文标题:SSE(服务器推送事件)的介绍、问题及解决

      本文链接:https://www.haomeiwen.com/subject/tydhoxtx.html