ActiveMQ实战(四)--ActiveMQ的通信方式之request-response(请求响应模式)

一、简介


在前面的两种模式中都是一方负责发送消息而另外一方负责处理。而我们实际中的很多应用相当于一种一应一答的过程,需要双方都能给对方发送消息。于是请求-应答的这种通信方式也很重要。它也应用的很普遍。


请求-应答方式并不是JMS规范系统默认提供的一种通信方式,而是通过在现有通信方式的基础上稍微运用一点技巧实现的。


下图是典型的请求-应答方式的交互过程:




在JMS里面,如果要实现请求/应答的方式,可以利用JMSReplyTo和JMSCorrelationID消息头来将通信的双方关联起来。另外,QueueRequestor和TopicRequestor能够支持简单的请求/应答过程。

現在,如果我们要实现这么一个过程,在发送请求消息并且等待返回结果的client端的流程如下:


// client side
Destination tempDest = session.createTemporaryQueue();
MessageConsumer responseConsumer = session.createConsumer(tempDest);
...

// send a request..
message.setJMSReplyTo(tempDest)
message.setJMSCorrelationID(myCorrelationID);

producer.send(message);

client端创建一个临时队列并在发送的消息里指定了发送返回消息的destination以及correlationID。那么在处理消息的server端得到这个消息后就知道该发送给谁了。


Server端的大致流程如下


public void onMessage(Message request) {  
  
  Message response = session.createMessage();  
  response.setJMSCorrelationID(request.getJMSCorrelationID())  
  producer.send(request.getJMSReplyTo(), response)  
}

这里我们是用server端注册MessageListener,通过设置返回信息的CorrelationID和JMSReplyTo将信息返回。

 以上就是发送和接收消息的双方的大致程序结构。


二、实战request-response(请求响应模式)通信


具体的实现代码如下:


Client:

package com.fendo.mq;


import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.util.Random;

/**
 * 客户端
 * @author fendo
 *
 */

public class Client implements MessageListener {
    private static int ackMode;
    private static String clientQueueName;
 
    private boolean transacted = false;
    private MessageProducer producer;
 
    static {
        clientQueueName = "client.messages";
        ackMode = Session.AUTO_ACKNOWLEDGE;
    }
 
    public Client() {
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection connection;
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            Session session = connection.createSession(transacted, ackMode);
            Destination adminQueue = session.createQueue(clientQueueName);
 
            //设置消息生成器将消息发送到服务器正在消耗的队列
            this.producer = session.createProducer(adminQueue);
            this.producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
            //创建一个临时队列,该客户端将侦听响应,
            //然后创建一个从该临时队列消耗消息的消费者...对于真正的应用程序,
            //客户端应该为服务器的每个消息重新使用相同的临时队列...一个临时队列 每个客户端
            Destination tempDest = session.createTemporaryQueue();
            MessageConsumer responseConsumer = session.createConsumer(tempDest);
 
            //此类也将处理到临时队列的消息
            responseConsumer.setMessageListener(this);
 
            //现在创建您要发送的实际消息
            TextMessage txtMessage = session.createTextMessage();
            // 设置信息
            txtMessage.setText("MyProtocolMessage");
 
            //将回复字段设置为上面创建的临时队列,这是服务器应答的队列...
            txtMessage.setJMSReplyTo(tempDest);
 
           //设置相关ID,以便当您收到响应时,您知道响应是哪个发送消息
           //如果没有多个未完成的消息给服务器,那么
           //相同的相关ID可以用于所有的消息...如果有多个未完成
           //消息到您可能想要将相关ID与此关联的服务器
		   //消息不知何故...一个地图很好
            String correlationId = this.createRandomString();
            txtMessage.setJMSCorrelationID(correlationId);
            this.producer.send(txtMessage);
        } catch (JMSException e) {
            //妥善处理异常
        }
    }
 
    private String createRandomString() {
        Random random = new Random(System.currentTimeMillis());
        long randomLong = random.nextLong();
        return Long.toHexString(randomLong);
    }
 
    public void onMessage(Message message) {
        String messageText = null;
        try {
            if (message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
                messageText = textMessage.getText();
                System.out.println("响应内容 = " + messageText);
            }
        } catch (JMSException e) {
            //妥善处理异常
        }
    }
 
    public static void main(String[] args) {
        new Client();
    }
}


这里的代码除了初始化构造函数里的参数还同时设置了两个destination,一个是自己要发送消息出去的destination,在session.createProducer(adminQueue);这一句设置。


另外一个是自己要接收的消息destination, 通过Destination tempDest = session.createTemporaryQueue(); responseConsumer = session.createConsumer(tempDest); 这两句指定了要接收消息的目的地。这里是用的一个临时队列。在前面指定了返回消息的通信队列之后,我们需要通知server端知道发送返回消息给哪个队列。于是txtMessage.setJMSReplyTo(tempDest);指定了这一部分,同时txtMessage.setJMSCorrelationID(correlationId);方法主要是为了保证每次发送回来请求的server端能够知道对应的是哪个请求。这里一个请求和一个应答是相当于对应一个相同的序列号一样。


    同时,因为client端在发送消息之后还要接收server端返回的消息,所以它也要实现一个消息receiver的功能。这里采用实现MessageListener接口的方式:

public void onMessage(Message message) {
        String messageText = null;
        try {
            if (message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
                messageText = textMessage.getText();
                System.out.println("messageText = " + messageText);
            }
        } catch (JMSException e) {
            //Handle the exception appropriately
        }
}



Server:


这里server端要执行的过程和client端相反,它是先接收消息,在接收到消息后根据提供的JMSCorelationID来发送返回的消息:

 public void onMessage(Message message) {
        try {
            TextMessage response = this.session.createTextMessage();
            if (message instanceof TextMessage) {
                TextMessage txtMsg = (TextMessage) message;
                String messageText = txtMsg.getText();
                response.setText(this.messageProtocol.handleProtocolMessage(messageText));
            }
 
            //从接收到的消息中设置相关ID为响应消息的相关ID
            //这可以让客户端识别该消息的响应
            //向服务器发送的一个未完成的消息
            response.setJMSCorrelationID(message.getJMSCorrelationID());
 
            //将响应发送到接收消息的JMSReplyTo字段指定的目的地,
            //这大概是客户创建的临时队列
            this.replyProducer.send(message.getJMSReplyTo(), response);
        } catch (JMSException e) {
            //妥善处理异常
        }
    }

前面,在replyProducer.send()方法里,message.getJMSReplyTo()就得到了要发送消息回去的destination。
另外,设置这些发送返回信息的replyProducer的信息主要在构造函数相关的方法里实现了:


    public Server() {
        try {
            //这个消息代理是嵌入的
            BrokerService broker = new BrokerService();
            broker.setPersistent(false);
            broker.setUseJmx(false);
            broker.addConnector(messageBrokerUrl);
            broker.start();
        } catch (Exception e) {
            //妥善处理异常
        }
 
        //将消息的处理委托给另一个类,在设置JMS之前实例化它,这样它就可以处理消息了
        this.messageProtocol = new MessageProtocol();
        this.setupMessageQueueConsumer();
    }
 
    private void setupMessageQueueConsumer() {
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(messageBrokerUrl);
        Connection connection;
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            this.session = connection.createSession(this.transacted, ackMode);
            Destination adminQueue = this.session.createQueue(messageQueueName);
 
            //设置一个消息生成器响应来自客户端的消息,我们将从一个消息发送到从jmsreplytoheader字段发送到的目的地
            this.replyProducer = this.session.createProducer(null);
            this.replyProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
 
            //设置消费者从管理队列中消费消息
            MessageConsumer consumer = this.session.createConsumer(adminQueue);
            consumer.setMessageListener(this);
        } catch (JMSException e) {
            //妥善处理异常
        }
    }

总体来说,整个的交互过程并不复杂,只是比较繁琐。对于请求/应答的方式来说,这种典型交互的过程就是Client端在设定正常发送请求的Queue同时也设定一个临时的Queue。同时在要发送的message里头指定要返回消息的destination以及CorelationID,这些就好比是一封信里面所带的回执。根据这个信息人家才知道怎么给你回信。对于Server端来说则要额外创建一个producer,在处理接收到消息的方法里再利用producer将消息发回去。这一系列的过程看起来很像http协议里面请求-应答的方式,都是一问一答。


MessageProtocol

package com.fendo.mq;

/**
 * 此类需要运行上面的客户端/服务器示例。 将消息处理委托给单独的类仅仅是个人喜好。
 * @author fendo
 *
 */

public class MessageProtocol {
    public String handleProtocolMessage(String messageText) {
        String responseText;
        // 判断是否是client传过来的信息,在这里就可以做些解析
        if ("MyProtocolMessage".equalsIgnoreCase(messageText)) {
            responseText = "我收到了信息";
        } else {
            responseText = "我不知道你传的是什么: " + messageText;
        }
         
        return responseText;
    }
}


三、运行测试


先运行客户端(请求),在ActiveMQ控制台进行查看,会多出一个记录






"Number Of Pending Messages "等待消费的数量为"1",进入队列的消息为"1",然后运行服务端(响应),会收到信息




再查看控制面板信息会变成如下所示





"Number Of Consumers"消费者为"1","Messages Enqueued"入队数量为"1","Messages Dequeued"出队数量为"1"。


完整示例: http://download.csdn.net/detail/u011781521/9907835


参考:http://activemq.apache.org/how-should-i-implement-request-response-with-jms.html

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值