ActiveMQ之Ajax调用

来源:互联网 发布:剑网三脸型数据成男 编辑:程序博客网 时间:2024/05/01 05:07

前言

ActiveMQ支持Ajax,这是应用在实时web应用中的一种异步的JavascriptXml机制。这意味着你可以利用ActiveMQ发布/订阅的天性,来创建高度实时的web应用。

Ajax允许一个常见的DHTML客户端(使用JavaScript和一个第5版及更高版本的现代浏览器)通过互联网收发信息。ActiveMQAjax的支持建立在与RESTActiveMQ连接器相同的基础上,该连接器允许任意可联网的设备通过JMS收发消息。

如果想看一下Ajax是怎么用的,跑一下 官方的例子 就行了。

Servlet

首先要在Web应用中安装AMQAjaxServlet,以此来支持基于AjaxJMS

...

  <servlet>

   <servlet-name>AjaxServlet</servlet-name>

   <servlet-class>org.apache.activemq.web.AjaxServlet</servlet-class>

  </servlet>

  ...

 <servlet-mapping>

   <servlet-name>AjaxServlet</servlet-name>

   <url-pattern>/amq/*</url-pattern>

  </servlet-mapping>

这个servlet既可以为需要的js文件提供服务,又可以处理JMS的请求和响应。

JavascriptAPI

ActiveMQajax特性是由客户端的 amq.js脚本提供的。从ActiveMQ 5.4开始,该脚本利用三个不同的适配器中的一个来支持与服务器的通信。流行的jQueryPrototype以及Dojo都是被支持的,并且这三个库的比较新的版本都随ActiveMQ发布了。

<script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>

<script type="text/javascript" src="js/amq_jquery_adapter.js"></script>

<script type="text/javascript" src="js/amq.js"></script>

<script type="text/javascript">

  var amq = org.activemq.Amq;

  amq.init({

    uri: 'amq',

    logging: true,

    timeout: 20

  });

</script>

包含这些脚本的结果就是创建了一个名叫amqjavascript对象,它提供了发送信息以及订阅频道和主题的API

发送一条消息

要从javascript客户端发送一条JMS消息,需要做的仅仅是调用这个方法:

amq.sendMessage(myDestination,myMessage);

这里的myDestination是目的地URL字符串地址(例如:"topic://MY.NAME"或者 "channel://MY.NAME"),myMessage是任意格式化好的XML或者被编码为XML内容的纯文本。

接收消息

要接收消息,客户端必须定义一个消息处理函数,并且将其注册到amq对象中。例如:

var myHandler =

{

  rcvMessage:function(message)

  {

     alert("received "+message);

  }

};

 

amq.addListener(myId,myDestination,myHandler.rcvMessage);

这里的myId是一个字符串标识符,在之后调用amq.removeHandler(myId)函数的时候会用到。myDestination目的地URL字符串地址(例如:"topic://MY.NAME"或者"channel://MY.NAME")。接收到消息的时候,回调函数myHandler.rcvMessage会把消息传递到你的处理代码块。

消息其实是文本消息的正文,或者对象消息的字符串(toString())表示形式。

注意,默认情况下,通过Stomp发布的消息如果包含了content-length消息头,则它将会被ActiveMQ转换为二进制消息,并且会对web客户端不可见。从ActiveMQ 5.4.0开始,你可以通过将amq-msg-type消息设置设为“text”,以使消息可以被web客户端消费。

对选择器的支持

默认情况下,一个ajax客户端会接收到其订阅的主题或队列上的所有消息。在ActiveMQ 5.4.1amq.js支持了JSM选择器,因为很多时候仅接收这些消息中的一个子集非常有用。选择器被作为调用amq.addListener函数的第四个可选参数。

amq.addListener( myId, myDestination,myHandler.rcvMessage, { selector:"identifier='TEST'" } );

这样用的时候,Javascript客户端只会收到包含了被设置为“TEST”“identifier”消息头的消息。

在多浏览器窗口中使用AMQ Ajax

单一浏览器的所有窗口或者tab页在ActiveMQ服务器中共享同一个JSESSIONID。除非服务器可以分辨多窗口的监听器,否则发送给一个窗口的消息可能被传递到另一个窗口。实际上,这意味着amq.js在任意时刻只能在一个浏览器窗口保持活跃。从ActiveMQ 5.4.2开始,这个问题被解决了,解决方法是允许对amq.init的每一次调用指定一个唯一的clientId。这样做之后,同一个浏览器的多个窗口就可以快乐地共存了。它们分别在代理上有单独的消息订阅者集合,并且不互相影响。

这个例子中,我们使用当前时间(web页面被加载的时间)作为唯一标识。只要两个浏览器窗口不是在同一毫秒打开的,这种方法就是有效的,并且包含在ActiveMQ发行版的例子 chat.html 中使用的就是这种方法。其他保证clientId唯一性的方法也会很容易设计出来。注意clientId只需要在一个session中唯一即可。(同一毫秒在不同浏览器中打开的窗口不会互相影响,因为他们属于不同的session。)

org.activemq.Amq.init({

  uri: 'amq',

  logging: true,

  timeout: 45,

  clientId:(new Date()).getTime().toString()

});

注意在一个tab页或者窗口中,clientId对所有的消息订阅者是公用的,并且它和作为amq.addListener函数的第一个参数的clientId是完全不同的。

·        amq.init中,clientId的作用是区分共享同样的JSESSIONID的不同的web客户端。在调用amq.init时,同一个浏览器的不同窗口需要一个唯一的clientId

·        amq.addListener中,clientId用于将一个消息订阅与回调函数相关联,当订阅收到一条消息的时候会出发这个函数。这些clientId值是每一个web页面内部的,不需要在多窗口或tab页中保持唯一性。

它是如何工作的

AjaxServlet MessageListenerServlet

在服务器端,amqajaxt特性是被继承自MessageListenerServletAjaxServlet处理的。这个servlet负责追踪既有的客户端(使用一个HttpSession),并慢慢地构造客户端收发消息需要的AMQjavax.jms对象(例如:目的地、消息消费者、消息可用监听器,即Destination,MessageConsumerMessageAVailableListener)。这个servlet应当被映射到服务于Ajax客户端的web应用上下文中的“/amq/*”路径下(这是可以改变的,但客户端javascriptamq.uri域需要做相应的修改。)

客户端发送消息

客户端发送一条消息的时候,它被编码为POST请求的内容,使用的是所支持的几个XmlHttpRequest连接适配器之一(jQueryPrototypeDojo)中的APIamq对象可能会将若干发送消息的请求合并到一个单独的POST中,当然这样做的前提是能做到不增加额外的延迟(看下面的轮询部分)。

当接MessageListenerServlet 收到一个POST请求,消息会被作为application/x-www-form-urlencoded参数解码,并带有类型“type”(在此情况下是“send”,而不是下面所说的“lisen”“unlisten”)和目的地。 如果一个目的地通道或者主题不存在,它将会被创建。消息将会作为一个文本消息(TextMessage)被发送到目的地。

监听消息

当客户端注册了一个监听器,消息订阅请求在一次POST请求中从客户端发送到服务器端,就像发送消息一样,只不过其类型“type”“listen”。当MessageListenerServlet接收到“listen”消息的时候,它就慢慢地创建一个MessageAvailableConsumer并为其注册一个监听器。 

等待轮询消息

当一个由MessageListenerServlet创建的监听器被调用,表明一条消息可用时,由于HTTP“客户端-服务器模式的限制,不可能直接把这条消息发送到ajax客户端。相反客户端必须对消息实施一种特殊类型的轮询。轮询通常意味着周期性的发送请求去查看时候有消息可用,这样的话就有一个折中的考虑:如果轮询的频率比较高,当系统空闲的时候就会产生过多的负载;反之如果轮询频率低,探测新消息的延迟就会变高。

为解决负载和延迟的折中问题,AMQ使用一种等待轮询机制。一旦amq.js脚本被加载,客户端就开始从服务器轮询可用消息。一个轮询请求可以作为一个GET请求发送,如果有其他准备从客户端发送到服务器端的消息,也可以作为一个POST请求发送。当MessageListenerServlet接收到一次轮询的时候,它将会:

1.   如果轮询请求是POST,所有的“send”“listen”、和“unlisten”消息都被处理。

2.   如果所有被订阅的通道或主题都没有对客户端可用的消息,该servlet将暂停请求的处理,直至:

o   一个MessageAvailableConsumer监听器被调用,表明现在有一条消息可用;或者

o   超时过期(通常是大约30秒,比所有常见的TCP/IP、代理和浏览器超时都少)。

3.   一个HTTP响应被返回到客户端,包含被封装成text/xml的所有可用消息。

amq.js javascript接收到轮询请求的响应时把所有消息传递到注册的处理函数,以此来处理它们。一旦处理完了所有的消息,它立即向服务器发送另一次轮询。

所以amq ajax特性的空闲状态是服务器上的一次轮询请求空档,等待消息被发送到客户端。这个空挡请求被一个超时设定周期性地刷新,防止任何 TCP/IP、代理或者浏览器的超时关闭连接。所以服务器能够通过唤醒空档请求并允许发送响应,来异步地向客户端发送一条消息。

客户端可以通过创建(或使用现有的)第二条连接,异步地向服务器发送一条消息。然而,在处理轮询响应的过程中,正常的客户端消息发送被暂停,因此所有要发送的消息进入队列,在处理过程结束时连同将要发送的轮询请求(无延迟),作为一个单独的POST进行发送。这确保了客户端和服务器之间只需要两个连接(对大多数浏览器来说)。

无线程(threadless)等待

上面描述的等待轮询是使用Jetty 6Continuations机制实现的。这允许与请求关联的线程在等待期间被释放,所以容器就不需要为每个客户端维护一个线程了(这可能是一个很大的数字)。如果使用了另一个servlet容器,Continuation机制退回到使用等待,并且线程不被释放。

服务器推送对比

首先我们可以很容易地为ActiveMQ增加服务器推送支持。然而由于多种原因,我们更喜欢Ajax方式:

·        使用Ajax意味着我们对每一次发送/接收使用一个清晰的HTTP请求,而不是维持一个无限长的GET,这样对web基础设施(防火墙,代理服务器,缓存等等)更加友好。

·        我们仍然可以利用HTTP 1.1的长连接套接字和管道处理,来达到将单个套接字用于客户端和服务器之间通讯的功效;虽然是以一种对任意有HTTP功能的基础设施都有效的方法。

·        服务器是纯REST的,所以对任意客户端有效(而不是被束缚到页面上使用的自定义JavaScript函数调用,这正是服务器推送方法需要的)。所以服务器推送将服务器约束到了web页面之上;而使用Ajax我们可以有一个对任意页面都有效的通用服务。

·        客户端可以通过轮询和超时的频率控制。例如,通过使用20秒超时的HTTP GET,可以避免服务器推送在某些浏览器中出现的内存问题。或者对轮询队列使用零超时GET

·        更容易充分利用消息的HTTP编码,而不是使用JavaScript函数调用作为传输协议。

·        服务器推送假定服务器知道客户端在使用什么函数,因为服务器主要负责通过套接字下发JavaScript函数调用 ——对我们来说更好的方法是发送通用的XML数据包(或者字符串或者其他任意格式的消息),并且让JavaScript客户端完全从服务器端解耦。

·        Ajax提供完全的XML支持,允许富消息的完整XML文档被传送到客户端,通过标准的JavaScript DOM支持可以很容易地处理这种消息。

 

0 0
原创粉丝点击