反向 Ajax,第 3 部分: Web 服务器和 Socket.IO

使用各种 Web 容器、API 和抽象库

系列 文章探讨了如何使用 Reverse Ajax 技术开发事件驱动的 Web 应用程序。反向 Ajax,第 1 部分:Comet 简介 介绍了实现 Reverse Ajax 通信的不同方式:轮询、搭载 (piggyback) 和 Comet,还介绍了如何使用长轮询 (long-polling) 和流。反向 Ajax,第 2 部分:WebSockets 介绍了如何使用 WebSocket 实现 Reverse Ajax,并探讨了如何使用 Comet 和 WebSockets 限制 Web 服务器。在本文中,您将了解如何在各种 Web 容器和 API 的 Web 应用程序中使用 Comet 和 WebSocket。还将了解 Socket.IO,它是一个可与 Reverse Ajax 一起在 Web 应用程序中使用的抽象库。您可以透明地使用这些抽想库,它们将所有复杂操作隐藏于 Comet 和 WebSocket 背后。

Mathieu Carbou, Java Web 架构师, Ovea

/developerworks/i/p-mcarbou.jpgMathieu Carbou 是 Ovea 公司的一名 Java Web 架构师和顾问,他提供服务和开发解决方案。他是几个开源项目的参与者和领导者、演讲人和蒙特利尔 Java 用户组的领导者。 Mathieu 具有很强的代码设计和最佳实践背景,是从客户端到后端事件驱动的 Web 开发方面的专家。他专注于为可高度扩展的 Web 应用程序提供事件驱动的消息传递解决方案。 请访问他的 博客



2012 年 7 月 30 日

简介

现今,用户都期待能够通过 Web 访问快速的动态访问应用。本 系列文章 向您展示如何使用 Reverse Ajax 技术来开发事件驱动的 Web 应用程序。反向 Ajax,第 1 部分:Comet 简介 介绍了 Reverse Ajax、轮询、流、Comet 和长轮询。在您了解了如何通过 HTTP 使用 Comet 之后,就会发现长轮询是可靠地实现 Reverse Ajax 的最佳方式,因为目前所有浏览器都提供了这方面的支持。反向 Ajax,第 2 部分:WebSockets 展示了如何使用 WebSocket 实现 Reverse Ajax。代码示例有助于说明 WebSocket、FlashSocket、服务器端的约束、请求范围内的服务和暂停历时长久的请求。

本文将深入研究如何在不同 Web 容器和 API 的 Web 应用程序(Servlet 3.0 和 Jetty Continuation)中使用 Comet 和 WebSocket。了解如何通过抽象库(如 Socket.IO)透明地使用 Comet 和 WebSocket。Socket.IO 使用功能检测来决定是否将通过 WebSocket、AJAX 长轮询或 Flash 等来创建连接。

您可以 下载本文使用的源代码

先决条件

在理想情况下,要最大限度地充分利用本文,则应该了解 JavaScript 和 Java。本文创建的示例是使用 Google Guice 构建的。Google Guice 是用 Java 编写的依赖项注入框架。要理解本文内容,则需要熟悉依赖项注入框架的概念,比如 Guice、Spring 或 Pico。

要运行本文中的示例,还需要使用最新版的 Maven 和 JDK (请参阅 参考资料)。


Comet 和 WebSocket 的服务器解决方案

您在 反向 Ajax,第 1 部分:Comet 简介 中应该已经了解到,Comet(长轮询或流)要求服务器在潜在的长延迟后能够暂停、重启或完成一个请求。反向 Ajax,第 2 部分:WebSockets 描述了服务器需要如何使用非阻塞 I/O 特性来处理一些连接,以及它们如何仅使用线程为请求提供服务(每请求线程模型)。您应该还了解到,WebSocket 的使用取决于服务器,并非所有的服务器都支持 WebSocket。

这一节将向您展示如何在 Jetty、Tomcat 和 Grizzly Web 服务器上使用 Comet 和 WebSocket(如果可以)。本文所提供的 源代码 包含了一个在 Jetty 和 Tomcat 上运行的简单聊天 Web 应用程序样例。本章节还将讨论支持 API 的下列应用服务器:Jboss、Glassfish 和 WebSphere。

Jetty

Jetty 是一个 Web 服务器,它支持 Java Servlet 规范 3.0、WebSocket 和其他的集成规范。Jetty 具有以下特征:

  • 强大而又灵活
  • 易于嵌入
  • 支持虚拟主机、会话集群和一些可以轻易通过 Java 代码进行配置的特性
  • 适用于 Google App Engine 的托管服务

核心的 Jetty 项目是由 Eclipse Foundation 管理。

自从版本 6 开始,Jetty 包含了一个异步 API,称为 Jetty Continuation,它可以充许暂停某个请求,稍后再重新开始该请求。表 1 显示了 Jetty 主要版本所支持的规范和 API 的图表。

表 1. Jetty 版本和支持
支持Jetty 6Jetty 7Jetty 8
非阻塞 I/OXXX
Servlet 2.5XXX
Servlet 3.0XX
Jetty Continuation (Comet)XXX
WebSocketXX

要通过 Comet 实现 Reverse Ajax ,可以使用 Jetty 所提供的 Continuation API,如下所示 清单 1:

清单 1. 用于 Comet 的 Jetty Continuation API
// Pausing a request from a servlet's method (get, post, ...):

protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException { 

Continuation continuation = ContinuationSupport.getContinuation(req); 
// optionally set a timeout to avoid suspending requests for too long 
continuation.setTimeout(0); 
// suspend the request 
continuation.suspend(); 
// then hold a reference for future usage from another thread 
continuations.offer(continuation); 

}

// Then, from another thread which wants to send an event to the client:

while (!continuations.isEmpty()) { 
    Continuation continuation = continuations.poll(); 
    HttpServletResponse response = 
        (HttpServletResponse) continuation.getServletResponse(); 
    // write to the response 
    continuation.complete(); 
}

完整的 Web 应用程序可在本文所附的 源代码 中找到。Jetty Continuation 存放在 JAR 文件中。您必须将此 JAR 文件放在 Web 应用程序的 WEB-INF/lib 文件夹中,以便能够使用 Jetty 的 Comet 特性。Jetty Continuation 也可以在 Jetty 6、Jetty 7 和 Jetty 8 上使用。

从 Jetty 7 开始,您还可以使用 WebSocket 特性。将 Jetty 的 WebSocket JAR 文件放入 Web 应用程序的 WEB-INF/lib 文件夹中,以便获得访问 Jetty 的 WebSocket API 的访问权,如 清单 2 中所示:

清单 2. Jetty 的 WebSocket API
// Implement the  doWebSocketConnect and returns an implementation of
//  WebSocket:

public final class ReverseAjaxServlet extends WebSocketServlet { 
    @Override 
    protected WebSocket doWebSocketConnect(HttpServletRequest request,
                                           String protocol) { 
        return [...] 
    } 
}

// Sample implementation of  WebSocket:

class Endpoint implements WebSocket { 
    Outbound outbound; 
    public void onConnect(Outbound outbound) { 
        this.outbound = outbound;    
    } 
    public void onMessage(byte opcode, String data) { 
        outbound.sendMessage("Echo: " + data);
        if("close".equals(data)) 
             outbound.disconnect();
    } 
    public void onFragment(boolean more, byte opcode, 
                           byte[] data, int offset, int length) { 
    } 
    public void onMessage(byte opcode, byte[] data, 
                          int offset, int length) { 
        onMessage(opcode, new String(data, offset, length)); 
    } 
    public void onDisconnect() { 
        outbound = null; 
    } 
}

可下载 源代码 中的 jetty-websocket 文件夹提供了一个聊天示例,它演示了如何使用 Jetty 提供的 WebSocket API。

Tomcat

Tomcat 可能是最广为人知的 Web 服务器。人们使用它已经很多年了,并且它已作为 Web 容器集成到早期版本的 Jboss 应用服务器中。Tomcat 还可以用作 servlet 规范的参考实现。servlet API 2.5 开始停用 Tomcat,人们开始关注基于非阻塞 I/O(如 Jetty)的替代物。表 2 显示了 Tomcat 两个最新版本支持的规范和 API。

表 2. Tomcat 支持
支持Tomcat 6Tomcat 7
非阻塞 I/OXX
Servlet 2.5XX
Servlet 3.0X
Advanced I/O (Comet)XX
WebSocket

如 表 2 中所示,Tomcat 并不支持 WebSocket;它使用一个与 Jetty Continuation 等效的对等物(叫 Advanced I/O)来支持 Comet。Advanced I/O 是一个包装 NIO 的低级包装器,比优秀的 API 更能促进 Comet 的使用。使用此 API 的应用程序示例相对贪乏,没有几个。清单 3 显示了在聊天 Web 应用程序中用于挂起请求和恢复使用请求的 servlet 示例。您可以在本文里的 源代码 中找到完整的 Web 应用程序。

清单 3. Comet 的 Tomcat API
public final class ChatServlet extends HttpServlet 
                               implements CometProcessor { 

    private final BlockingQueue<CometEvent> events = 
         new LinkedBlockingQueue<CometEvent>(); 

    public void event(CometEvent evt) 
        throws IOException, ServletException { 

        HttpServletRequest request = evt.getHttpServletRequest(); 
        String user = 
            (String) request.getSession().getAttribute("user"); 
        switch (evt.getEventType()) { 
            case BEGIN: { 
                if ("GET".equals(request.getMethod())) { 
                    evt.setTimeout(Integer.MAX_VALUE); 
                    events.offer(evt); 
                } else { 
                    String message = request.getParameter("message"); 
                    if ("/disconnect".equals(message)) { 
                        broadcast(user + " disconnected"); 
                        request.getSession().removeAttribute("user"); 
                        events.remove(evt); 
                    } else if (message != null) { 
                        broadcast("[" + user + "]" + message); 
                    } 
                    evt.close(); 
                } 
            } 
        } 
    } 

    void broadcast(String message) throws IOException { 
        Queue<CometEvent> q = new LinkedList<CometEvent>(); 
        events.drainTo(q); 
        while (!q.isEmpty()) { 
            CometEvent event = q.poll(); 
            HttpServletResponse resp = event.getHttpServletResponse();
            resp.setStatus(HttpServletResponse.SC_OK);
            resp.setContentType("text/html");
            resp.getWriter().write(message);
            event.close(); 
        } 
    } 
}

在 Tomcat 里,异步 servlet 必须实现一个 CometProcessor。对于异步 servlet,Tomcat 并不调用标准 HTTP 方法(doGetdoPost 等)。相反,会向 event(CometdEvent) 方法发送一个事件。在请求到达时,该示例会查看是否为了挂起该请求而使用了一个 GET 方法;不必调用 evt.close() 。如果调用的是一个 POST 方法,则表示用户发送了一个消息,该消息将传播至其他 CometEvent,并且可以调用 evt.close() 来完成请求的传送。在用户端,广播会让所有的长轮询请求来完成消息发送,并且会立即发送另一个长轮询请求来接收下一个事件。

Grizzly 和 Glassfish

Grizzly 并不是一个 Web 容器,它更像是一个帮助开发人员构建可伸缩性应用程序的 NIO 框架。它发展为 Glassfish 项目的一部分,但也可以单独或嵌套使用它。Grizzly 提供了充当 HTTP/HTTPS 服务器的部件,还为 Bayeux Protocol、Servlet、HttpService OSGi 和 Comet 等提供部件。Grizzly 支持 WebSocket,并且可以在 Glassfish 中使用它来支持 Comet 和 WebSocket。

Glassfish(Oracle 的应用服务器)是 J2EE 6 规范的参考实现。Glassfish 是一个完整的套件(像 WebSphere 和 Jboss 一样),它用 Grizzly 来支持 NIO、WebSocket 和 Comet。它的模块架构(基于 OSGI)使得更改部件变得非常灵活。表 3 显示了 Glassfish 对 Comet 和 WebSocket 的支持。

表 3. Glassfish 支持
支持Glassfish 2Glassfish 3
非阻塞 I/OXX
Servlet 2.5XX
Servlet 3.0X
CometXX
WebSocketX

Grizzly 的使用并不繁琐,可以在 Java 代码中嵌套使用或直接使用它。人们普遍将它用作一个框架,用它来支持可以嵌套在大型应用程序(如 Glassfish)中的 Comet 和 WebSocket,这提供了 Web 部署功能和 Servlet 规范 API。

查看 参考资料 中关于 Grizzly 或 Glassfish 中的 WebSocket 和 Comet 示例的链接。因为 Glassfish 使用了 Grizzly,所以两个示例都有效。WebSocket API 与Jetty 中的非常相似,但是 Comet API 更复杂一些。

Jboss

Jboss 是构建于 Tomcat 之上的应用服务器。从版本 5 起,它就开始支持 Comet 和 NIO。Jboss 7 还在开发中,但下面的 表 4 中包含该版本。

表 4. Jboss 支持
支持Jboss 5Jboss 6Jboss 7
非阻塞 I/OXXX
Servlet 2.5XXX
Servlet 3.0XX
CometXXX
WebSocket

WebSphere

WebSphere 是一个 IBM 应用服务器。WebSphere V8(参阅 参考资料,阅读相关声明)添加了对 Servlet 3 API(包括 Comet 的标准化异步 API)的支持。

表 5. WebSphere 支持
支持WebSphere 8
非阻塞 I/OX
Servlet 2.5X
Servlet 3.0X
CometX
WebSocket

使用通用 API 会怎样?

每个服务器都自带了用于 Comet 和 WebSocket 的本机 API 。正如您所猜测的,编写一个便携版的 Web 应用程序会非常困难。Servlet 3.0 Specification 包含挂起请求并稍后重新使用请求的其他方法,并充许所有支持 Servlet 3.0 Specification 的 Web 容器支持 Comet 长轮询请求。

Jetty 团队提供了一个名叫 Jetty Continuation 的库,该库独立于 Jetty 容器。Jetty Continuation 库可以智能地检测容器或规范是否可用。如果在 Jetty 服务器上运行,则会使用本机 Jetty API 。如果在支持 Servlet 3.0 规范的容器上运行,则会使用通用的 API。否则会使用不可伸缩的实现。

关于 WebSocket,Java 中没有相关的标准,因此,如果您想要使用 WebSocket,则需要在 Web 应用程序中使用容器供应商 API 。

表 6 概括了各种服务器支持的技术

表 6. 服务器支持的技术
容器CometWebSocket
Jetty 6Jetty ContinuationN/A
Jetty 7Servlet 3.0
Jetty Continuation
Native Jetty API
Jetty 8Servlet 3.0
Jetty Continuation
Native Jetty API
Tomcat 6Advanced I/ON/A
Tomcat 7Servlet 3.0
Advanced I/O
Jetty Continuation
N/A
Glassfish 2Native Grizzly APIN/A
Glassfish 3Servlet 3.0
本机 Grizzly API
Jetty Continuations
本机 Grizzly API
Jboss 5本机 Jboss APIN/A
Jboss 6Servlet 3.0
本机 Jboss API
Jetty Continuation
N/A
Jboss 7Servlet 3.0
本机 Jboss API
Jetty Continuations
N/A
WebSphere 8Servlet 3.0
Jetty Continuation
N/A

关于 WebSocket, 除了使用容器 API ,没有其他的明确方案。至于 Comet,支持 Servlet 3.0 Specification 的所有容器都支持 Comet。Jetty Continuation 的优势是它在所有的这些容器上都提供了对 Comet 的支持。因此,一些 Reverse Ajax 库(在 下一节 和 本 系列 文章中的下一篇文章中会讨论它)都在对其服务器端 API 使用 Jetty Continuation。

Jetty Continuation API 在本文的 Jetty 示例 中曾描述过。Servlet 3.0 Specification 在本系列 第 1 部分:Comet 简介 的两个 Comet 示例中使用和描述过。


抽象库

考虑到所有主要的 API(Servlet 3.0 和 Jetty Continuation)、服务器端的所有本机支持以及在客户端实现 Reverse Ajax 的两种主要方法(Comet 和 WebSocket),编写您自己的 JavaScript 和 Java 代码以便将它们连接在一起会非常困难。您还必须考虑超时、连接故障、确认、排序和缓冲等因素。

本文的其余部分将向您介绍 Socket.IO。本 系列 的第 4 部分将探讨 Atmosphere 和 CometD。这三个库均为开源库,它们都支持在许多服务器上使用 Comet 和 WebSocket。

Socket.IO

Socket.IO 是一个 JavaScript 客户端库,可提供一个与 WebSocket 类似的 API,用该 API 连接到远程服务器,以便异步发送和接收消息。通过提供通用 API,Socket.IO 可支持多种传输:WebSocket、Flash Sockets、长轮询、流、forever Iframes 和 JSONP 轮询。Socket.IO 检测浏览器功能并尝试选择可用的最佳传输。Socket.IO 库几乎与所有的浏览器(包括旧版浏览器,如 IE 5.5)以及移动浏览器都兼容。它还提供了心跳、超时、断开连接和错误处理等功能。

Socket.IO 网站(参阅 参考资料)详细描述了库的工作原理以及使用哪种浏览器和 Reverse Ajax 技术。基本上,Socket.IO 使用一种可以使客户端库与服务器端的端点通信的通信协议,以便能够读懂 Socket.IO 协议。Socket.IO 最初是为 Node JS 开发的,它使用一个 JavaScript 引擎来构建快速服务器。许多项目都支持其他语言,其中包括 Java。

清单 4 显示了一个在客户端使用 Socket.IO JavaScript 库的示例。Socket.IO 网站中包含一些文档和示例。

清单 4. Socket.IO 客户端库的使用
var socket = new io.Socket(document.domain, { 
    resource: 'chat' 
}); 
socket.on('connect', function() { 
    // Socket.IO is connected
}); 
socket.on('disconnect', function(disconnectReason, errorMessage) { 
    // Socket.IO disconnected
}); 
socket.on('message', function(mtype, data, error) { 
    // The server sent an event
});

// Now that the handlers are defined, establish the connection:
socket.connect();

要使用 Socket.IO JavaScript 库,则需要一个称为 Socket.IO Java 的相应 Java 部件(参阅 参考资料)。该项目最初由 Apache Wave 团队创建,创建于推出 WebSocket 之前,可利用该项目为 Reverse Ajax 提供支持。Socket.IO Java 是 Ovea(一家专门从事事件驱动 Web 开发的公司)的分支,由 Ovea 维护,后来遭到遗弃。由于有多种传输方式,所以开发后端来支持 Socket.IO 客户端库非常复杂。本系列的第 4 部分将展示如何支持客户端库中的许多传输,这种支持并不是获得更好的可伸缩性和浏览器支持所必需的,因为有长轮询和 WebSocket 就已经足够。在 WebSocket 尚未发布的时候,Socket.IO 确实是一个不错的选择。

Socket.IO Java 使用 Jetty Continuation API 来挂起和重新开始使用请求。它使用本机 Jetty WebSockets API 来支持 WebSocket。您可以使用 Socket.IO Java 确定哪一个服务器将与 Web 应用程序协同工作。

下面的 清单 5 显示了一个如何在服务器上使用 Socket.IO 的示例。您必须定义一个扩展 SocketIOServlet 的 servlet ,并实现返回某种端点表示形式的方法。此 API 与 WebSocket API 非常相似。该 API 的优势在于它可在服务器端使用,独立于客户端所选择的传输方式。Socket.IO 将所有的传输类型转化为与服务器端的 API 相同。

清单 5. 聊天示例 servlet 中使用的 Socket.IO Java 库
public final class ChatServlet extends SocketIOServlet { 
    private final BlockingQueue<Endpoint> endpoints = 
        new LinkedBlockingQueue<Endpoint>(); 
 
    @Override 
    protected SocketIOInbound doSocketIOConnect
                                        (HttpServletRequest request) { 
        String user = 
                (String) request.getSession().getAttribute("user"); 
        return user == null ? null : new Endpoint(this, user, request); 
    } 
 
    void broadcast(String data) { 
        for (Endpoint endpoint : endpoints) { 
            endpoint.send(data); 
        } 
    } 
 
    void add(Endpoint endpoint) { 
        endpoints.offer(endpoint); 
    } 
 
    void remove(Endpoint endpoint) { 
        endpoints.remove(endpoint); 
    } 
}

清单 6 显示了如何返回端点。

清单 6. 在聊天示例 Endpoint 中使用的 Socket.IO Java 库
class Endpoint implements SocketIOInbound { 
 
    [...]
 
    private SocketIOOutbound outbound; 
 
    [...]

    @Override 
    public void onConnect(SocketIOOutbound outbound) { 
        this.outbound = outbound; 
        servlet.add(this); 
        servlet.broadcast(user + " connected"); 
    } 
 
    @Override 
    public void onDisconnect(DisconnectReason reason, 
                             String errorMessage) { 
        outbound = null; 
        request.getSession().removeAttribute("user"); 
        servlet.remove(this); 
        servlet.broadcast(user + " disconnected"); 
    } 
 
    @Override 
    public void onMessage(int messageType, String message) { 
        if ("/disconnect".equals(message)) { 
            outbound.close(); 
        } else { 
            servlet.broadcast("[" + user + "] " + message); 
        } 
    } 
 
    void send(String data) { 
        try { 
            if (outbound != null 
    && outbound.getConnectionState() == ConnectionState.CONNECTED) { 
                outbound.sendMessage(data); 
            } 
        } catch (IOException e) { 
            outbound.close(); 
        } 
    } 
 
}

Socket.IO 的完整示例包含在 socketio 文件夹内的 源代码 中。


结束语

所有 Web 容器都支持 Comet,并且几乎都支持 WebSocket。即使规范会导致几种不同的本机实现 ,您仍然可以使用 Comet 和 API(Servlet 3.0 或 Jetty Continuation)来开发 Web 应用程序。甚至,您还可以使用诸如 Socket.IO 之类的库来透明地使用 Comet 和 WebSocket。还有另两个库(Atmosphere 和 CometD)将在本 系列 的下一篇文章中讨论。这三个库提供了多浏览器支持、美妙的用户体验,以及错误处理、更易于使用的 API、超时以及重新连接带来的好处。


下载

描述名字大小
文章源代码reverse_ajaxpt3_source.zip21KB

参考资料

学习

获得产品和技术

  • Jetty:获得 Jetty、Web 服务器和 javax.servlet 容器,此外,提供对 WebSockets 的支持。
  • Apache Tomcat Advanced I/O documentation:获得有价值的链接、用户指南、参考和 Apache Tomcat Development 说明。
  • Grizzly NIO 框架:帮助您充分利用 Java NIO API 的优势。
  • Grizzly Comet 例子:了解在重新构建新的应用程序之前需要对现有应用程序进行的修改。
  • Glassfish 应用服务器:获得主要的 GlassFish Server 开源版本。
  • Socket.IO Java:获得原项目和最新的 Ovea 派生产品。
  • Apache Maven:获得 Maven,这是一个软件项目管理和理解工具。
  • Java Development Kit, Version 6:获得 Java Platform, Standard Edition (Java SE),它使您能够在桌面和服务器以及当今充满挑战的嵌入环境中开发和部署 Java 应用程序。
  • 免费试用 IBM 软件。下载试用版本,登录在线试用版本,在沙箱环境中使用产品,或是通过云来访问它。有超过 100 种 IBM 产品试用版可供您选择。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=828177
ArticleTitle=反向 Ajax,第 3 部分: Web 服务器和 Socket.IO
publish-date=07302012