`

反向 Ajax,第 4 部分: Atmosphere 和 CometD

阅读更多

简介

本 系列 文章向您展示如何使用反向 Ajax 技术开发事件驱动的 Web 程序。第 1 部分 介绍了 Reverse Ajax、轮询、流、Comet 和长轮询。第 2 部分 介绍了如何使用 WebSocket 实现 Reverse Ajax,还讨论了使用 Comet 和 WebSocket 的 Web 服务器的限制。第 3 部分 探讨了当您需要支持多个服务器或提供一个用户可以自己的服务器上部署的独立 Web 应用程序时,您实现自己的 Comet 或 WebSocket 通信系统的过程中会遇到的一些困难。即使客户端上的 JavaScript 代码很简单,您仍然需要一些异常处理、重新连接和确认功能。在服务器端,由于缺少全局 API 以及一些 Web 服务器 API,导致需要使用附带抽象功能的框架。第 3 部分还讨论了Socket.IO。

在本文中,您将了解 Atmosphere 和 CometD,它们是用于 Java 服务器的最有名的开源反向 Ajax 库。

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

先决条件

在理想的情况下,如果想最大限度地利用本文,您应该了解 JavaScript 和 Java。要运行本文中的示例,则需要使用最新版的 Maven 和 JDK(参阅 参考资料)。

 

Atmosphere 框架

Atmosphere 是一种 Java 技术框架,它提供了一个通用 API,以便使用许多 Web 服务器(包括 Tomcat、Jetty、GlassFish、Weblogic、Grizzly、JBossWeb、JBoss 和 Resin)的 Comet 和 WebSocket 特性。它支持任何支持 Servlet 3.0 Specification 的 Web 服务器。在本系列文章展示的所有框架中,Atmosphere 支持的服务器最多。

Atmosphere 可以针对 Comet,Atmosphere 检测本机服务器 API 并切换回 Servlet 3.0 的运行时环境。或者将退回到 "托管" 异步模式(但不具有 Jetty Continuations 的可伸缩性),这也适用于 Comet。Atmosphere 已面市两年多了,仍然处于积极开发阶段。它被应用于大型 Web 应用程序中,比如 JIRA,这是最有名的问题追踪器之一。图 1 显示了 Atmosphere 框架。

图 1. Atmosphere 的构架视图

Atmosphere 框架的构架视图

Atmosphere 框架由 Atmosphere 运行时所组成,为各种不同 Web 服务器解决方案和标准提供了一个通用 API。在运行时上面,客户端可以通过设置 servlet 并使用 Google Web Toolkit (GWT) 来访问 API 和反向 Ajax 特性。或者,您还可以使用 Jersey,这是一种用于实现 JSR-311(JAX-RS 规范)的框架。因此,可以使用注释的方式将 Atmosphere 用于更多其它的 REST 场景中。配置您选择的模块之后,就可以通过实现一些类(本文稍后进行讨论)来访问 Atmosphere 运行时。您还可以随意使用所提供的插件,添加对集群、消息传递、依赖注入的支持。如果您正在使用一个 Web 框架(Wicket、Struts 或 Spring MVC),您可以通过使用 Atmosphere 的 MeteorServlet 明确添加对反向 Ajax 的支持。该 servlet 展示了Meteor 对象,可以在控制器和服务中检索该对象,以便挂起或重新开始请求。

Atmosphere 的优势仍然在于服务器端:它提供了一个标准化 API,该 API 包含与 WebSockets 或 Comet 进行通信的所有不同的解决方案和方法。Atmosphere 没有使用客户端和服务器之间通信的协议,如 Socket.IO 和 CometD。这两个库均提供了客户端 JavaScript 和服务器端 servlet,使用特定的协议(用于握手、消息收发、确认和心跳的协议)进行通信。Atmosphere 的目标是在服务器端提供一个通用的通信信道。如果需要使用特定的协议,比如 Bayeux(CometD 使用的协议),则必须在 Atmosphere 中开发您自己的 “处理程序”。CometD 插件可以满足您的需求:它利用 Atmosphere 的 API 来挂起和重新开始请求,并通过使用 Bayeux 协议来委托 CometD 类来管理 CometD 通信。

Atmosphere 提供了一个 jQuery 客户端库,该库可以使连接设置变得更容易,它能够自动检测可以使用的最佳传输协议(WebSockets 或 CometD)。Atmosphere 的 jQuery 插件的用法与 HTML5 WebSockets API 相似。首先要连接到服务器,注册一个回调来接收消息,然后再放入一些数据。

本文所提供的 源代码 中包含了一个 Atmosphere 样例,该样例直接将处理程序用于 Atmosphere servlet。客户端代码始终保持不变;与本 系列第 1 部分、第 2 部分、第 3 部分文章中所用的代码相同(都位于使用 Comet 长轮询的聊天样例代码中)。您还可以使用 Atmosphere 的 jQuery 插件,但这并不是必需的,因为 Atmosphere 不强迫使用任何通信协议。强烈建议您查看 Atmosphere 项目中的其他示例,特别是那些使用 JSR-311 注释 (Jersey) 的示例。它们确实简化了处理程序的编写。

清单 1 显示了 Atmosphere 处理程序的接口。

清单 1. AtmosphereHandler 接口
public interface AtmosphereHandler<F, G> { 
    void onRequest(AtmosphereResource<F, G> resource) 
        throws IOException; 
    void onStateChange(AtmosphereResourceEvent<F, G> event) 
        throws IOException; 
    void destroy(); 
}

onRequest 方法接收到来自客户端的所有请求,并决定是否挂起或重新开始请求(或不执行任何操作)。每次挂起或重新开始一个请求、发送一条广播或者出现超时,都会通过 onStateChange 方法发送和接收一个事件。

清单 2 中显示了 Comet 聊天应用程序的 onRequest 方法的实现。

清单 2. AtmosphereHandler 接口 - onRequest
Broadcaster broadcaster = 
        BroadcasterFactory.getDefault().lookup(
        DefaultBroadcaster.class, ChatHandler.class.getName(), true); 
broadcaster.setScope(Broadcaster.SCOPE.APPLICATION); 
resource.setBroadcaster(broadcaster); 
HttpServletRequest req = resource.getRequest(); 
String user = (String) req.getSession().getAttribute("user"); 
if (user != null) { 
    if ("GET".equals(req.getMethod())) { 
        resource.suspend(-1, false); 
    } else if ("POST".equals(req.getMethod())) { 
        String cmd = req.getParameter("cmd"); 
        String message = req.getParameter("message"); 
        if ("disconnect".equals(cmd)) { 
            close(resource); 
        } else if (message != null && message.trim().length() > 0) {
            broadcaster.broadcast("[" + user + "] " + message); 
        } 
    } 
}

典型的惯例是挂起 GET 请求,并使用 POST 请求发送消息。收到消息后,会向所有在广播装置内注册的资源进行广播。注意,该示例未向HttpServlet 输出流方面写入任何内容。广播或挂起操作只发送其他实现方法接收的事件,如 清单 3 中所示:

清单 3. AtmosphereHandler 接口 - onStateChange
Broadcaster broadcaster = 
    BroadcasterFactory.getDefault().lookup(
        DefaultBroadcaster.class, ChatHandler.class.getName(), true); 
// Client closed the connection. 
if (event.isCancelled()) { 
    close(event.getResource()); 
    return; 
} 
try { 
    String message = (String) event.getMessage(); 
    if (message != null) { 
        PrintWriter writer = 
            event.getResource().getResponse().getWriter(); 
        writer.write(message); 
        writer.flush(); 
    } 
} finally { 
    if (!event.isResumedOnTimeout()) { 
        event.getResource().resume(); 
    } 
}

现在您需要一个可正常运转的 Comet 聊天应用程序。简言之,重要的 Atmosphere 概念是:代表连接的资源对象,以及负责触发资源事件并决定挂起或重新开始请求的事件的广播装置。注意,这只是一个 Comet 示例。想要能够使用 WebSocket 和 Comet,则需要使用一个客户端库,以及一个更复杂的处理程序。

表 1 下表概述了使用 Atmosphere 框架的利弊。

表 1. Atmosphere 的利弊
利 弊

如果您必须在无法控制的多个 Web 服务器中部署一个 Web 应用程序。同时因为 Atmosphere 支持使用多台 Web 服务器,所以它是让应用程序的反向 Ajax 特性正确发挥其作用的更好的选择。

当您需要在原始的反向 Ajax 通信上使用通用 API,如果没有任何已定义好的协议,那么您可能想要开发或扩展协议。

缺少有关 Atmosphere 的架构、项目、概念和 API 的文档,如果您需要研究源代码或分析提供的示例,这些文档对您会很有帮助。与其他框架的简单 API(如 Socket.IO 和 ometD)相比,该 API 的技术性更强,虽然有时有些难懂。即使用了 Atmosphere 注释,有些名称和属性仍然具有很强的技术性。

尽管在服务器端有出色的抽象功能,但没有良好的客户端库,也没有协议,所以其他所有功能都留待开发人员来开发。如果您需要使用高级的超时检测、确认、回退和跨域等功能,特别是在移动设备上使用这些功能时,目前的库太过于简单,无法满足大型、可伸缩 Web 应用程序的需要。在这种情况下,CometD 更可靠一些;它使用了一个可用来激活某些控制流和错误检测的通信协议,CometD 中提供了所有这些功能。如果您需要额外的特性,使用 CometD JavaScript 客户端和 Atmosphere CometD 插件是一个很不错的替代方案。

 

CometD 框架

CometD 框架是基于 HTTP 的事件驱动通信解决方案,已面市多年。CometD 框架的第 2 版本中添加了对注释配置和 WebSocket 的支持。CometD 框架提供了一个 Java 服务器部件和一个 Java 客户端部件,还有一个基于 jQuery 和 Dojo 的 JavaScript 客户端库。CometD 使用了一个叫 Bayeux 的标准化通信协议,充许您针对于消息确认、流控制、同步、集群等方面进行扩展。

CometD 的事件驱动方法非常适合事件驱动 Web 开发的新概念。正如传统的桌面用户界面那样,所有的组件均通过总线进行通信,以便发送通知和接收事件。因此所有的通信都是异步的。

CometD 框架:

  • 有大量的相关资料。
  • 提供了一些示例和 Maven 原型,以促进项目的启动。
  • 拥有精心设计的 API,支持扩展开发。
  • 提供了一个称为 Oort 的集群模块,该模块使您能够运行多个CometD Web 服务器,将它们看作是负载均衡器背后的集群中的节点,从而扩展许多 HTTP 连接。
  • 支持安全策略,允许更具体地配置由谁通过哪条通道发送消息。
  • 更好地与 Spring 和 Google Guice(依赖注入框架)集成。

Bayeux 协议

Bayeux 通信协议主要是基于 HTTP。它提供了客户端与服务器之间的响应性双向异步通信。Bayeux 协议基于通道进行通信,通过该通道从客户端到服务器、从服务器到客户端或从客户端到客户端(但是是通过服务器)路由和发送消息。Bayeux 是一种 “发布- 订阅” 协议。CometD 实现了 Bayeux 协议,除了 Comet 和 WebSocket 传输协议之外,它还提供了一个抽象层,以便通过 Bayeux 发送请求。

服务器和内部构件

CometD 与三个传输协议绑定在一起:JSON、JSONP 和 WebSocket。他们都依赖于 Jetty Continuations 和 Jetty WebSocket API。在默认情况下,可以在 Jetty 6、Jetty 7、和 Jetty 8 中以及其他所有支持 Servlet 3.0 Specification 的服务中使用 CometD。可以使用与扩展相同的方式添加和开发传输协议。您应该能够编写支持 Grizzly WebSocket API 和其他 API 的传输协议,然后再在配置 CometD 服务器的步骤中添加这些协议。图 2 显示了主要 CometD 数据块的概述。

图 2. CometD 的构架视图

CometD 框架的构架视图,由通过 Bayeux 协议通信的客户端库和服务器库组成。CometD 支持扩展和不同的传输层(WebSockets、Ajax 长轮询等),还支持服务器上的多个 API。

图 2图中没有显示访问消息通道的安全层。

本文提供的 源代码 包括一个使用 CometD 的 Web 应用程序。该 Web 应用程序的描述符包括 清单 4 中的定义,以便使用该聊天示例。

清单 4. web.xml
<servlet> 
    <servlet-name>cometd</servlet-name> 
    <servlet-class>
        org.cometd.java.annotation.AnnotationCometdServlet
    </servlet-class> 
    <async-supported>true</async-supported> 
    [...]
    <init-param> 
        <param-name>services</param-name> 
        <param-value>ChatService</param-value> 
    </init-param> 
    <init-param> 
        <param-name>transports</param-name> 
        <param-value>
            com.ovea.cometd.websocket.jetty8.Jetty8WebSocketTransport
        </param-value> 
    </init-param> 
</servlet>

CometD servlet 支持一些控制全局设置的选项,比如能够设置传输协议和服务的选项。在本示例中,假设您想要为 Jetty 8 添加 WebSocket 支持。服务器端的 CometD 服务类 ChatService 会控制每个人说话的聊天室,如 清单 5 中所示:

清单 5. CometD ChatService
@Service 
public final class ChatService { 

    @Inject 
    BayeuxServer server; 

    @PostConstruct 
    void init() { 
        server.addListener(new BayeuxServer.SessionListener() { 
            @Override 
            public void sessionAdded(ServerSession session) { 
                [...]
            } 

            @Override 
            public void sessionRemoved(ServerSession session, boolean timedout) {
                [...]
            } 
        }); 
    } 

    @Configure("/**") 
    void any(ConfigurableServerChannel channel) { 
        channel.addAuthorizer(GrantAuthorizer.GRANT_NONE); 
    } 

    @Configure("/chatroom") 
    void configure(ConfigurableServerChannel channel) { 
        channel.addAuthorizer(new Authorizer() { 
            @Override 
            public Result authorize(
                [..] // check that the user is in session
            } 
        }); 
    } 

    @Listener("/chatroom") 
    void appendUser(ServerSession remote, 
                    ServerMessage.Mutable message) { 
        [...]
    } 

}

清单 5 演示了 CometD 的一些重要特性,其中包括 :

  • 依赖注入
  • 生命周期管理
  • 全局信道配置
  • 安全管理
  • 消息转换(在所有消息前添加用户名)
  • 会话管理

在客户端,该示例没有进行任何扩展,只是使用原始的 CometD 代码,如 清单 6 中所示:

清单 6. CometD 客户端代码
// First create t cometd object and configure it

var cometd = new $.Cometd('CometD chat client'); 
cometd.configure({ 
    url: document.location + 'cometd', 
    logLevel: 'debug' 
}); 
cometd.websocketEnabled = 'WebSocket' in window;

// Then we register some listeners. Meta channels (those with 
// the form /meta/<name> are specific reserved channels)

cometd.addListener('/meta/disconnect', function(message) { 
    [...]
}); 

cometd.addListener('/meta/connect', function(message) { 
    [...]
});

// Then, starting a connexion can be done using:

cometd.handshake();

// And subscriptions with:

cometd.subscribe('/chatroom', function(event) { 
    [...] //  event.data holds the message
});

// We finally send data to the chatroom like this:

cometd.publish('/chatroom', msg);

CometD 的客户端 API 功能强大、可以扩展并且易于理解和使用。本文只介绍了 Web 应用程序的主要部件,所以请参见 示例应用程序,以便更好地了解 CometD 的强大。

表 2 概括了使用 CometD 框架的利与弊。

表 2. CometD 的利与弊
利 弊

CometD 提供了一套从客户端到服务器端以及从独立 Java 客户端到服务器的完整解决方案。该框架拥有大量的相关文档、一个出色的 API,并且易于使用。最重要的是,它拥有一个事件驱动方法。CometD 和 Bayeux 是许多事件驱动 Web 应用程序的组成部分。其他的反向 Ajax 框架并不提供任何事件驱动机制,这迫使最终用户必须开发自己的定制解决方案。

CometD 支持许多必要的特性,如重新连接、可靠的超时检测、回退、批处理、消息确认以及更多您在其他反向 Ajax 框架中无法找到的特性。CometD 允许实现最可靠、延迟最短的通信。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics