WebSocket--消息推送框架

这篇文章主要讲述B/S架构中服务器“推送”消息给浏览器。内容涉及ajax论询(polling),comet(streaming,long polling)。后面会附上源代码。

最近在工做有这么一个需求,须要在门户首页获取服务器“推送”过来的消息,通常首先想到的是用ajax。本着好奇的精神,到网上查了一下,相关方面的知识,收获还真不小,记录下分享给你们。javascript

通常要实现网页的消息提醒,不外乎两种状况:html

  1. 客户端主动定时的去拿服务器端,有消息就提醒(polling);
  2. 服务器主动"推送"消息给客户端,这里说的主动推送,并非真的,而是客户端申请了须要显示消息提醒的信息,而服务端暂时没给客户端答复,把请求hold住了。。(comet)。

"服务器推"推技术简介

基于HTTP长链接的"服务器推"技术
  • 基于html file流(streaming,浏览器不兼容)
  • iframe streaming(streming的扩展,浏览器兼容)
  • 基于ajax长轮询(long-polling,浏览器兼容)
基于客户端套接口的"服务器推"技术
  • Flash XML Socket
  • Java Applet 套接口这两种都不是咱们这篇文章要说的主题,并且小林也没往这方面研究,由于,偶的应用是跑在weblogic的j2ee程序。

示例环境

eclipse+tomcat前端

struts1.3+jsp+jqueryjava

本代码中全部示例都是在eclipse+tomcat下运行经过的,浏览器使用ie9+chrome进行测试,运用了struts+jquery框架,来辅助实现。若是你熟悉strust的配置,能够跳过下面,直接看polling。jquery

web.xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <display-name>test</display-name>
  <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config/struts-config-push.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
</web-app>web

struts的配置ajax

新建/WEB-INF/struts-config/struts-config-push.xml加入以下内容chrome

  <?xml version="1.0" encoding="UTF-8"?>
   <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
   "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
 <struts-config>
    <action-mappings>
      <action path="/push/comet" parameter="method" type="com.linjunlong.test.push.action.CometAction" ></action> 
    </action-mappings>
 </struts-config>数据库

polling

 在介绍comet以前,先介绍一些传统的ajax轮询(polling),轮询最简单也最容易实现,每隔一段时间向服务器发送查询,有更新再触发相关事件。对于前端,使用js的setInterval以AJAX或者JSONP的方式按期向服务器发送request。他可让用户不须要刷新浏览器,也能够即时的获取服务器更新。apache

前端jsp代码

咱们新建一个在/WebContent/push下新建一个polling.jsp页面,把jquery脚本复制到/WebContent/static/js/jquery-1.8.0.min.js下。下面是polling.jsp代码,脚本部分咱们设置每3秒进行一次轮询。

<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBK"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=GBK">
  <script type="text/javascript" src="../../static/js/jquery-1.8.0.min.js"></script>
  <title >polling test</title>
</head>
<body>
  <div id="message"></div>
</body>
<script type="text/javascript">
  var polling = function(){
    $.post('../comet.do?method=polling', function(data, textStatus){
      $("#message").append(data+"<br>");
    });
  };
  interval = setInterval(polling, 3000);
</script>
</html>

后端action代码

咱们在com.linjunlong.test.push.action.CometAction中添加polling方法

public ActionForward polling(ActionMapping mapping,ActionForm form,HttpServletRequest request,HttpServletResponse response) throws Exception{
  System.out.println("-----CometAction.polling.start");
  PrintWriter writer = response.getWriter();
  //TODO 编写一些CRUD的代码进行数据库查询,看看用户需不须要弹出消息提醒
  writer.print("your hava a new message");
  writer.flush();
  writer.close();
  System.out.println("-----CometAction.polling.end");
  return null;
}

效果展现

当咱们启动tomcat,在浏览器中输入http://localhost:8080/test/push/comet/polling.jsp,浏览器上就不断的显示咱们的ajax从后台获取的信息了。

 

下面是chrome developer tool中url请求的信息,能够看到,ajax轮询就是不断的在后端进行访问。。若是服务器反映慢一点。。前面一个请求还没相应完,后面一个请求已经发送。会怎么样呢?

 

采用这种方式要获取即便的消息推送,并且应用可能须要集群,小林想估计要弄一个队列表,而后模块有须要向某我的推送一条消息的话,就须要插入一条信息到数据库,而后客户端ajax访问后台,后台进行数据库查询,看当前用户在队列表里是否有记录,有的话,就取出来,返回给ajax,而后删除数据库中的记录。。。(这些都是小林想固然的啦,偶还没开始作。。。)

经过chrome的开发工具能够看到,浏览器不断的向后台进行请求(若是用户多的话,这得要多大的并发啊,估计压力测试,服务器直接挂了。)。并且每次请求服务器端不必定有数据返回,用在聊天系统还好说,小林只是想在门户首页弄个提醒而已啊,您有新的短消息,您有新的邮件- -。。。这种也许开一天浏览器都不必定有一条消息的- -。

comet

基于Comet的技术主要分为流(streaming)方式和长轮询(long-polling)方式。 首先看Comet这个单词,不少地方都会说到,它是“彗星”的意思,顾名思义,彗星有个长长的尾巴,以此来讲明客户端发起的请求是长连的。即用户发起请求后就挂起,等待服务器返回数据,在此期间不会断开链接。流方式和长轮询方式的区别就是:对于流方式,客户端发起链接就不会断开链接,而是由服务器端进行控制。当服务器端有更新时,刷新数据,客户端进行更新;而对于长轮询,当服务器端有更新返回,客户端先断开链接,进行处理,而后从新发起链接。 会有同窗问,为何须要流(streaming)和长轮询(long-polling)两种方式呢?是由于:对于流方式,有诸多限制。若是使用AJAX方式,须要判断XMLHttpRequest 的 readystate,即readystate==3时(数据仍在传输),客户端能够读取数据,而不用关闭链接。问题也在这里,IE 在 readystate 为 3 时,不能读取服务器返回的数据,因此目前 IE 不支持基于 Streaming AJAX,而长轮询因为是普通的AJAX请求,因此没有浏览器兼容问题。另外,因为使用streaming方式,控制权在服务器端,而且在长链接期间,并无客户端到服务器端的数据,因此不能根据客户端的数据进行即时的适应(好比检查cookie等等),而对于long polling方式,在每次断开链接以后能够进行判断。因此综合来讲,long polling是如今比较主流的作法(如facebook,Plurk)。 接下来,咱们就来对流(streaming)和长轮询(long-polling)两种方式进行演示。

streaming

前端jsp代码

/test/WebContent/push/comet/streaming.jsp,脚本中有个游标pos由于服务器端是一段一段的发送消息过来的。

<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBK"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=GBK">
  <script type="text/javascript" src="../../static/js/jquery-1.8.0.min.js"></script>
  <title >streaming test</title>
</head>
<body>
  <div id="message"></div>
</body>
<script type="text/javascript">
  try {
    var request = new XMLHttpRequest(); 
  } catch (e) {
    alert("Browser doesn't support window.XMLHttpRequest");
  }

  var pos = 0;
  request.onreadystatechange = function () {
    if (request.readyState === 3) { 
      var data = request.responseText; 
      $("#message").append(data.substring(pos));
      pos = data.length;
    }
  };
  request.open("POST", "../comet.do?method=streaming", true);
  request.send(null); 
</script>
</html>

后端action代码

咱们在com.linjunlong.test.push.action.CometAction中添加polling方法

public ActionForward streaming(ActionMapping mapping,ActionForm form,HttpServletRequest request,HttpServletResponse response) throws Exception{
  System.out.println("-----CometAction.streaming.start");
  StreamingThread st = new StreamingThread(response);
  st.run();
  System.out.println("-----CometAction.streaming.end");
  return null;
}

下面是StreamingThread的代码。

public class StreamingThread extends Thread {
  private HttpServletResponse response = null;

  public StreamingThread(HttpServletResponse response){
    this.response = response;
  }

  @Override
  public void run() {
    try{
      String message = "your hava a new message";
      PrintWriter writer = response.getWriter();
      for(int i = 0 ,max = message.length(); i < max ; i++) {
        writer.print(message.substring(i,i+1));
        writer.flush();
        sleep(1000);
      }
      writer.close();
    }catch (Exception e) {}
  }
}

StreamingThread逻辑上是把咱们想要输出的内容一个一个输出,每输出一个字,而后就休眠1秒钟,其实这个类想表达的意思是,服务器端接收到客户端想要获取信息的请求,能够先不作任何操做,只要永远不调用writer.close(); 服务器端就随时能够给客户端发送消息。这里的精髓是writer.flush(); sleep(1000);

效果展现

在浏览器中输入http://localhost:8080/test/push/comet/streaming.jsp,浏览器上就一个字一个字的显示咱们从后端取得的信息了。

 

这里能够看到这里请求数只有一个,可是请求时间却很长,在这很长的时间里,服务器只要一有消息就能够主动的推送消息过来。不过缺点就是。浏览器不兼容(ie下没法实现),为了达到浏览器兼容,因而就有了下面的即便iframe-streaming

iframe-streaming

这也是早先的经常使用作法。首先咱们在页面里放置一个iframe,它的src设置为一个长链接的请求地址。Server端的代码基本一致,只是输出的格式改成HTML,用来输出一行行的Inline Javascript。因为输出就获得执行,所以就少了存储游标(pos)的过程。

前端jsp代码

/test/WebContent/push/comet/iframe.jsp中编写下面代码

 

<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBK"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=GBK">
  <script type="text/javascript" src="../../static/js/jquery-1.8.0.min.js"></script>
  <title >streaming test</title>
</head>
<body>
  <div id="message"></div>
  <iframe id="iframe" src="about:blank" style="display: none;" ></iframe>
</body>
<script type="text/javascript">
  var add_content = function(str){
    $("#message").append(str);
  };
  $(document).ready(function(){
    $("#iframe")[0].src="../comet.do?method=iframe";
  });
</script>
</html>

能够看到咱们在这jsp中定义了一个隐藏的iframe,他的地址是空的,由于在ie下,若是你写入一个地址,那么浏览器就会一直打转,给人一种页面还未加载万的假象,因而这里小林采用延迟加载的方式去等页面加载完再初始化请求地址

后端action代码

咱们在com.linjunlong.test.push.action.CometAction中添加iframe方法

public ActionForward iframe(ActionMapping mapping,ActionForm form,HttpServletRequest request,HttpServletResponse response) throws Exception{
  System.out.println("-----CometAction.iframe.start");
  IframeThread st = new IframeThread(response);
  st.run();
  System.out.println("-----CometAction.iframe.end");
  return null;
}

下面是IframeThread代码,与streaming逻辑上同样,只是输出的时候采用返回html脚本片断的方式,调用父页面的add_content 函数进行进行消息的添加,界面上的显示效果和streaming方式无异。

public class IframeThread extends Thread {
  private HttpServletResponse response = null;

  public IframeThread(HttpServletResponse response){
    this.response = response;
  }

  @Override
  public void run() {
    try{
      String message = "your hava a new message";
      PrintWriter writer = response.getWriter();
      for(int i = 0 ,max = message.length(); i < max ; i++) {
        writer.print("<script>parent.add_content('"+message.substring(i,i+1)+"');</script>");
        writer.flush();
        sleep(1000);
      }
      writer.close();
    }catch (Exception e) {
    // TODO: handle exception
    }
  }
}

用这种方式能够解决跨浏览器问题。

long-polling

长轮询是如今最为经常使用的方式,和流方式的区别就是服务器端在接到请求后挂起,有更新时返回链接即断掉,而后客户端再发起新的链接。不少大型网站都用这种技术。

前端jsp代码

/test/WebContent/push/comet/long.jsp中编写下面代码

<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBK"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=GBK">
  <script type="text/javascript" src="../../static/js/jquery-1.8.0.min.js"></script>
  <title >polling test</title>
</head>
<body>
  <div id="message"></div>
</body>
<script type="text/javascript">
  var updater = {
    poll: function(){
      $.ajax({

      url: "../comet.do?method=longPolling", 
      type: "POST", 
      dataType: "text",
      success: updater.onSuccess,
      error: updater.onError

      });
    },
    onSuccess: function(data, dataStatus){
      try{
        $("#message").append(data);
      }
      catch(e){
        updater.onError();
        return;
      }
      interval = window.setTimeout(updater.poll, 0);
    },
    onError: function(){
      console.log("Poll error;");
    }
  };
  updater.poll();
</script>
</html>

 

后台action代码

public ActionForward longPolling(ActionMapping mapping,ActionForm form,HttpServletRequest request,HttpServletResponse response) throws Exception{
  System.out.println("-----CometAction.longPolling.start");
  PrintWriter writer = response.getWriter();
  Thread longThread = new Thread() {
    public void run() {
      try {
        //这里模拟全局事件监听,若是其余模块有须要发送消息就发送一个事件,而后休眠中止,发送消息。
        sleep(5000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    };
  };
  longThread.run();
  writer.print("your hava a new message");
  writer.flush();
  writer.close();
  System.out.println("-----CometAction.longPolling.end");
  return null;
 }

这里咱们代码中,模拟型的休息5秒钟(其实要表达的意思是,这里让服务器hold住这个请求访问,等待服务器有消息了,就推送给用户)

效果

这里每次请求恰好5秒钟左右,可是实际运用中可能不止这么久。

WebSocket:将来方向

如今,不少网站为了实现即时通信,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对伺服器发出HTTP request,而后由伺服器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器须要不断的向伺服器发出请求,然而HTTP request 的header是很是长的,里面包含的数据可能只是一个很小的值,这样会占用不少的带宽和服务器资源。

而比较新的技术去作轮询的效果是comet,使用了AJAX。但这种技术虽然可达到双向通讯,但依然须要发出请求,并且在Comet中,广泛采用了长连接,这也会大量消耗服务器带宽和资源。

面对这种情况,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽并达到实时通信。

总结

文章只是简单的演示了实现消息推送的方式,并无比较系统的解决如何进行消息推送。实际过程当中若是咱们想要用于实战,可能要考虑客户端和服务器端直接的交流,服务器的压力,全局消息队列等等等。。。。。。。

最后附上源代码下载

------------------ 你分享 我学习 ------------------

声明:本文转自 http://www.cnblogs.com/divenswu/p/3520043.html 感谢您与我分享!

相关文章
相关标签/搜索