RabbitMQ Java开发 订阅模式

实现目标:类似 广播的效果 服务器发消息,两个客户端都能收到 全部的消息

P:生产者,也就是要发送消息的程序
C:消费者:消息的接受者,会一直等待消息到来。
queue:消息队列,图中红色部分
而在订阅模型中,多了一个exchange角色,而且过程略有变化:
P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
C:消费者,消息的接受者,会一直等待消息到来。
Queue:消息队列,接收消息、缓存消息。
Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
Fanout:广播,将消息交给所有绑定到交换机的队列
Direct:定向,把消息交给符合指定routing key 的队列
Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
Publish/Subscribe发布与订阅模式
发布订阅模式:
1、每个消费者监听自己的队列。
2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收 到消息。

 服务器端( 消息生产者 )

package com.joincall.j3c.JoinCallCC.RabbitMQ;

import com.rabbitmq.client.BuiltinExchangeType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitMQHelper extends Thread {
    protected static final Logger logger = LoggerFactory.getLogger(RabbitMQHelper.class);

    private boolean _Connected=false;//连接 RabbitMQ 成功true 或 失败false
    private Channel _RabbitMQ_Ch;
    public String _strMsg;
    private String _exchangeName="message_exchange"; //交换机名称
    //private String _strTaskQueue;//任务队列 不用了 使用多队列

    //线程 执行
    public void run() {
         this.SendMsg(_strMsg);
    }

    //-----------------------------------订阅模式 多队列广播 --------------------------------------------------------------
    //初始化 连接 RabbitMQ
    public void InitRabbitMQ(String strIP,String strPost,String strUserName,String strPwd ){
        try {
            //System.out.println("初始化 连接 RabbitMQ");
            //System.out.println("JoinCallCC InitRabbitMQ() 初始化 连接 RabbitMQ " +strIP +" "+strPost +" "+strUserName+" "+strPwd+" "+strTaskQueue +" ");
            logger.info("RabbitMQHelper InitRabbitMQ() 初始化 连接 RabbitMQ " +strIP +" "+strPost +" "+strUserName+" "+strPwd+" " +" ");
            //创建连接工厂,并设置连接信息
            ConnectionFactory f = new ConnectionFactory();
            //f.setHost("192.168.1.100");
            //f.setPort(5672);//可选,5672是默认端口
            //f.setUsername("admin");
            //f.setPassword("admin");
            f.setHost(strIP);
            f.setPort(Integer.parseInt(strPost));//可选,5672是默认端口
            f.setUsername(strUserName);
            f.setPassword(strPwd);
            //与rabbitmq服务器建立连接,rabbitmq服务器端使用的是nio,会复用tcp连接,并开辟多个信道与客户端通信,以减轻服务器端建立连接的开销
            Connection c = f.newConnection();
            //建立信道
            //Channel ch = c.createChannel();
            _RabbitMQ_Ch= c.createChannel();
            this._Connected=true;

            /** public DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal,Map<String, Object> arguments) throws IOException {
             return this.exchangeDeclare(exchange, type.getType(), durable, autoDelete, arguments);
             String exchange 交换机名称
             BuiltinExchangeType type    交换机类型
             DIRECT("direct"), 点对点交换机
             FANOUT("fanout"),  广播形式的交换机
             TOPIC("topic"),   通配符形式的交换机
             HEADERS("headers");  很少用不做学习
             boolean durable   是否持久化
             boolean autoDelete   是否自动删除
             boolean internal   内部 一般设置为false
             Map<String, Object> arguments  参数
             }*/
            this._exchangeName="message_exchange"; //交换机名称
            //创建交换机
            this._RabbitMQ_Ch.exchangeDeclare(this._exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
            //创建队列
            String queueA = "task_queue";//队列名称 -----------------------------------------------
            String queueB = "agent_msg_log";//队列名称 -----------------------------------------------
            /** public com.rabbitmq.client.AMQP.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
             queue  队列的名称
             durable 是否持久化,当mq重启之后还在
             exclusive 是否独占,只有一个消费者监听这个队列
             当connection 关闭的时候删除这个队列
             autoDelete  是否自动删除,没有消费者的时候删除
             arguments   参数
             }*/
            this._RabbitMQ_Ch.queueDeclare(queueA,true,false,false,null);
            this._RabbitMQ_Ch.queueDeclare(queueB,true,false,false,null);
            /**queueBind(String queue, String exchange, String routingKey)
             * queue 队列名称
             * exchange 交换机名称
             * routingKey 路由key
             *      如果交换机是 fanout就设置为空字符串  **/
            // 绑定交换机和队列
            this._RabbitMQ_Ch.queueBind(queueA,this._exchangeName,"");
            this._RabbitMQ_Ch.queueBind(queueB,this._exchangeName,"");

            //发送一个 启动消息
            String body = "rabbitMQ 订阅模式 启动成功";
            /**public void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException {
             exchange 交换机名称,简单模式下的交换机会默认使用 ""
             routingKey  路由名称
             props  配置信息
             body   消息体  */
            this._RabbitMQ_Ch.basicPublish(this._exchangeName,"",null,body.getBytes());

            logger.info("RabbitMQHelper InitRabbitMQ() 初始化 连接 RabbitMQ 成功! " +"--------------------------");
            logger.info("RabbitMQHelper InitRabbitMQ() 队列A "+ queueA +"--------------------------");
            logger.info("RabbitMQHelper InitRabbitMQ() 队列B "+ queueB +"--------------------------");
        }catch (Exception ex){
            //System.out.println("初始化 连接 RabbitMQ 失败");
            logger.error("RabbitMQHelper InitRabbitMQ() 初始化 连接 RabbitMQ 失败!" +strIP +" "+strPost +" "+strUserName+" "+strPwd+" "+" "+ ex.toString() +" "+ ex.getMessage());
        }
    }

    //发送
    public void SendMsg(String strMsg) {
        try {
            if(this._RabbitMQ_Ch==null){
                //logger.warn("JoinCallCC SendMsg() 没有启动连接RabbitMQ " +strMsg );
                return;
            }
            if(this._Connected!=true){
                logger.error("JoinCallCC SendMsg() 向RabbitMQ发送消息失败! 因连接RabbbitMQ失败!" +strMsg );
                return;
            }
            //logger.debug("===RabbitMQHelper SendMsg() RabbitMQ发消息:" +strMsg  );

            //System.out.println("JoinCallCC SendMsg() RabbitMQ发送消息 " +strMsg );
            //发布消息 这里把消息向默认交换机发送.默认交换机隐含与所有队列绑定,routing key即为队列名称
            //参数含义:
            //-exchange: 交换机名称,空串表示默认交换机"(AMQP default)",不能用 null
            //-routingKey: 对于默认交换机,路由键就是目标队列名称
            //-props: 其他参数,例如头信息
            //-body: 消息内容byte[]数组
            //RabbitMQ_Ch.basicPublish("", "task_queue", null, "Hello world!".getBytes());
            //_RabbitMQ_Ch.basicPublish("", "task_queue", null, strMsg.getBytes());
            //_RabbitMQ_Ch.basicPublish("", this._strTaskQueue, null, strMsg.getBytes());
            //订阅模式
            this._RabbitMQ_Ch.basicPublish(this._exchangeName,"",null,strMsg.getBytes());


        } catch (Exception ex) {
            //System.out.println("oinCallCC RabbitMQ_SendMsg() RabbitMQ发送消息 失败 "+strMsg );
            logger.error("JoinCallCC SendMsg() 向RabbitMQ发送消息失败! " + ex.toString() + " " + ex.getMessage());
        }
    }
}

调用方法

String strRabbitMQ_Ip ="192.168.1.100";
 String strRabbitMQ_Port = "5672";
 String strRabbitMQ_UserName = "admin";
 String strRabbitMQ_Pwd ="admin";

 //初始化 连接 RabbitMQ ----------------------------
 RabbitMQHelper rabbitMQHelper = new RabbitMQHelper();
 rabbitMQHelper.InitRabbitMQ(strRabbitMQ_Ip, strRabbitMQ_Port, strRabbitMQ_UserName, strRabbitMQ_Pwd );

发消息

rabbitMQHelper.SendMsg("发消息测试===============");

客户端( 消息消费者 )

package com.JavaRabbitMQClient;

import java.io.IOException;
//import java.sql.Connection;
import com.rabbitmq.client.*;
import com.rabbitmq.client.Connection;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;

import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;

import com.JavaRabbitMQClient.dbMySql.*;
/**
 * Hello world!
 *
 */
//消费者
public class App {
    protected static final Logger logger = LoggerFactory.getLogger(App.class);

    public static void main(String[] args) throws Exception {
        {
            //System.out.println("Hello World!");
            logger.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"  );
            logger.info("@@@@@@@@@@@@@@@@@ 启动 JavaRabbitMQClient @@@@@@@@@@@@@@@@@@@@@@@"  );
            logger.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"  );
            //logger.info("-------------------------------------------------"  );

            //RabbitMQ 参数
 
            String strRabbitMQ_Ip ="192.168.1.100";//
            String strRabbitMQ_Port = "5672";
            String strRabbitMQ_UserName = guest";
            String strRabbitMQ_Pwd = "guest";
            //System.out.println( "pbx "+strPbxIp+" " + strPbxPort +" "+ strApiPwd +"---------- ");
            logger.info("RabbitMQ 参数 IP:" + strRabbitMQ_Ip + " 端口:" + strRabbitMQ_Port + " 用户名:" + strRabbitMQ_UserName + " 密码:" + strRabbitMQ_Pwd + "  ---------- ");

            //连接工厂
            ConnectionFactory f = new ConnectionFactory();
            
            //f.setHost("192.168.1.100");//192.168.1.100  192.168.10.220
            //f.setPort(5672);//可选,5672是默认端口
            //f.setUsername("admin");//guest
            //f.setPassword("admin");//admin 

            f.setHost(strRabbitMQ_Ip);//192.168.1.100  192.168.10.220
            f.setPort(Integer.parseInt(strRabbitMQ_Port));//可选,5672是默认端口
            f.setUsername(strRabbitMQ_UserName);//admin
            f.setPassword(strRabbitMQ_Pwd);//admin

            //建立连接
            Connection c = f.newConnection();
            //建立信道
            final Channel ch = c.createChannel();
            //声明队列,如果该队列已经创建过,则不会重复创建
            String queueName = "agent_msg_log";//队列名称 ------ 坐席消息日志用 ------------------------------------------
            ch.queueDeclare(queueName,true,false,false,null);
            //System.out.println("等待接收数据");
            logger.info("RabbitMQ 声明队列:" + queueName + "  已连接 " +  " 等待接收数据 "  + "  ----------------------------- ");


            //收到消息后用来处理消息的回调对象
            DeliverCallback callback = new DeliverCallback() {
                @Override
                public void handle(String consumerTag, Delivery message) throws IOException {
                    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
                    String strDataTime=df.format(new Date());
                    //System.out.println(df.format(new Date()));// new Date()为获取当前系统时间

                    String msg = new String(message.getBody(), "UTF-8");
                    //System.out.println(strDataTime+" 收到: "+msg);
                    //遍历字符串中的字符,每个点使进程暂停一秒
                    for (int i = 0; i < msg.length(); i++) {
                        if (msg.charAt(i)=='.') {
                            try {
                                //Thread.sleep(1000);//暂停1秒
                                Thread.sleep(20);
                            } catch (InterruptedException e) {
                            }
                        }
                    }

                    recMsgEvent(msg);//处理收到的消息------------------------------------------------------
                    //System.out.println("处理结束");
                    //参数1:消息标签,参数2:是否确认多条消息
                    ch.basicAck(message.getEnvelope().getDeliveryTag(),false);
                }
            };

            //消费者取消时的回调对象
            CancelCallback cancel = new CancelCallback() {
                @Override
                public void handle(String consumerTag) throws IOException {
                }
            };
            //一次只能接受一条数据
            ch.basicQos(1);

            //第二个参数为消息回执,消息确认处理完成,为true为自动确认,只要消息发送到消费者即消息处理成功;为false为,手动发送确认回执,服务器才认为这个消息处理成功
            ch.basicConsume(queueName, false, callback, cancel);
        }
    }

    public static void recMsgEvent(String msg){
        try {
            //分析 收到的RabbitMQ消息-----------------
            recMsgEvent_Analyse(msg);
        }catch (Exception ex)
        {
            logger.debug("收到消息时出错! recMsgEvent() " +msg);
        }

    }

}

测试结果

 点击交换机名称出现

总结
交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。

发布订阅模式与工作队列模式的区别
1、工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机。
2、发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使用默认交换机)。
3、发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑 定到默认的交换机 。


 注:在实践中发现如果用工作队列模式(循环模式),如果有两个客户端(消息消费者)则服务器发出的消息先给A客户端,再有消息再给B客户端,交替分发。不能实现广播的效果。

        如果两个客户端都要收到全部的消息(广播效果)则需要使用 订阅模式

感谢:https://blog.csdn.net/qq_38063429/article/details/112350952

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值