使用HTTP长轮询来实现Comet

环境

  • java
  • tomcat7及以上
  • servlet 3.0

服务器端

Servlet 3.0 之前,一个普通 Servlet 的主要工作流程大致如下:首先,Servlet 接收到请求之后,可能需要对请求携带的数据进行一些预处理;接着,调用业务接口的某些方法,以完成业务处理;最后,根据处理的结果提交响应,Servlet 线程结束。而Servlet 3.0 支持异步处理:首先,Servlet 接收到请求之后,可以将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,此时 Servlet 还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有 ServletRequest 和 ServletResponse 对象的引用),或者将请求继续转发给其它 Servlet。如此一来, Servlet 线程不再是一直处于阻塞状态以等待业务逻辑的处理,而是启动异步线程之后可以立即返回。 默认情况下,Servlet 和过滤器并没有开启异步处理特性,如果希望使用该特性,则必须按照如下的方式启用:

  • 对于使用传统的部署描述文件 (web.xml) 配置 Servlet 和过滤器的情况,Servlet 3.0 为标签增加了子标签,该标签的默认取值为 false,要启用异步处理支持,则将其设为 true 即可。

    1
    2
    3
    4
    5
    6
    7
    8
    <servlet>
    <servlet-name>msgPush</servlet-name>
    <servlet-class>com.servlet.MsgPushServlet</servlet-class>
    <async-supported>true</async-supported>
    </servlet>
    <filter>
    <async-supported>true</async-supported>
    </filter>
  • 对于使用 Servlet 3.0 提供的 @WebServlet 和 @WebFilter 进行 Servlet 或过滤器配置的情况,这两个注解都提供了 asyncSupported 属性,默认该属性的取值为 false,要启用异步处理支持,只需将该属性设置为 true 即可。

  • servlet示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    public final class MsgPushServlet extends HttpServlet {

    private final Queue<AsyncContext> asyncContexts = new ConcurrentLinkedQueue<AsyncContext>();

    private final Random random = new Random();

    private final Thread generator = new Thread("Event generator") {

    @Override
    public void run() {
    while (!Thread.currentThread().isInterrupted()) {
    try {
    Thread.sleep(random.nextInt(5000)); //睡眠一段时间
    while (!asyncContexts.isEmpty()) { //对队列中的异步请求进行处理
    AsyncContext asyncContext = asyncContexts.poll();
    HttpServletResponse peer = (HttpServletResponse) asyncContext.getResponse();
    peer.getWriter().write(new JSONArray().put("At " + new Date()).toString());
    peer.setStatus(HttpServletResponse.SC_OK);
    peer.setContentType("application/json");
    asyncContext.complete();
    }
    } catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    } catch (IOException e) {
    throw new RuntimeException(e.getMessage(), e);
    }
    }
    }
    };

    @Override
    public void init() throws ServletException {
    generator.start();
    }

    @Override
    public void destroy() {
    generator.interrupt();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    AsyncContext asyncContext = req.startAsync();
    asyncContext.setTimeout(0);
    asyncContexts.offer(asyncContext);
    }
    }

客户端

1
2
3
4
5
6
function long_polling() {
  $.getJSON(url, function(events) {
    processEvents(events); //处理后台返回的结果
    long_polling();
  });
}

参考

1 初识Comet技术
2 反向Ajax,第1部分:Comet介绍
3 Servlet 3.0 新特性详解