• 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)   {
            try {
                //System.out.println("Hello World!");
                logger.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"  );
                logger.info("@@@@@@@@@@@@@@@@@ 启动 JavaRabbitMQClient @@@@@@@@@@@@@@@@@@@@@@@"  );
                logger.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"  );
                //logger.info("-----------------  queue_log4j  ---------------------------------"  );
    
                Properties prop = readConfigFile("config.properties");
                //MySql 参数
                MySqlUtil.mysql_open = prop.getProperty("mysql_open");
                MySqlUtil.mysql_url = prop.getProperty("mysql_url");
                MySqlUtil.mysql_username = prop.getProperty("mysql_username");
                MySqlUtil.mysql_password = prop.getProperty("mysql_password");
                MySqlUtil.mysql_driver = prop.getProperty("mysql_driver");
                logger.info("MySql 参数: " + prop.getProperty("mysql_url") + " -- " + prop.getProperty("mysql_username") + " -- " + prop.getProperty("mysql_password") + " -- " + prop.getProperty("mysql_driver"));
                if (MySqlUtil.mysql_open.equals("true") == false) {
                    logger.warn("没有启动连接 MySql 数据库 " + MySqlUtil.mysql_open + " " );
                }else {
                    try {
                        String str = MySqlUtil.connectionTest();//连接测试
                        logger.info("连接测试结果: " + str);
                        logger.info("==========================连接 MySql 成功!===========================");
    
                    } catch (Exception e) {
                        logger.info("连接 MySql 失败 " + e.toString() + " ===========================");
                    }
                }
    
    
                //RabbitMQ 参数
                String strRabbitMQ_Open = prop.getProperty("rabbitmq_open");//是否 开启 RabbitMQ
                String strRabbitMQ_Ip = prop.getProperty("rabbitmq_ip");//
                String strRabbitMQ_Port = prop.getProperty("rabbitmq_port");
                String strRabbitMQ_UserName = prop.getProperty("rabbitmq_username");
                String strRabbitMQ_Pwd = prop.getProperty("rabbitmq_pwd");
                //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");
                        //如果中文乱码则换GB2312 或 GBK 试试
                        String msg = new String(message.getBody(),"GB2312");
    
                        //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);
    
            }catch (Exception ex)
            {
                logger.error("JavaRabbitMQClient 启动时出错! App.main() " +ex.toString());
            }
        }
    
        public static void recMsgEvent(String msg){
            //DEBUG、INFO、WARN、ERROR和FATAL。这五个级别是有顺序的,DEBUG < INFO < WARN < ERROR < FATAL
            try {
                /*
                String strLevel = msg.substring(0, 4);
                if (strLevel.equals("INFO") == true) {
                    logger.info(msg);
                } else if (strLevel.equals("DEBU") == true) {
                    logger.debug(msg);
                } else if (strLevel.equals("WARN") == true) {
                    logger.warn(msg);
                } else if (strLevel.equals("ERRO") == true) {
                    logger.error(msg);
                } else {
                    logger.info(msg);
                }*/
                //分析 收到的RabbitMQ消息-----------------
                recMsgEvent_Analyse(msg);
            }catch (Exception ex)
            {
                logger.debug("收到消息时出错! recMsgEvent() " +msg);
            }
    
        }
    
        //分析 收到的RabbitMQ消息
        public static void recMsgEvent_Analyse(String strMsg){
            //是否包含
            boolean isInclude = strMsg.contains("<agentMsgLog>");
    
            if(isInclude==true){
                //包含
                recMsgEvent_AgentMsgLog(strMsg);////收到 坐席状态消息
            }else{
                //不包含
    
            }
    
        }
    
        //收到 坐席状态消息
        public static void recMsgEvent_AgentMsgLog(String strMsg){
            String strMsgJson=strMsg.replace("<agentMsgLog>","");
    
            //String str=getEncoding(strMsg);
            //logger.error("!!!!!!!!!!!!!!!!!!! " +str  +"  !!!!!!!  "+strMsg);
    
    
            logger.debug("收到 坐席状态消息: " +strMsg);
    
            //<agentMsgLog>{"msgType":"AgentSend","dateTime":"2022-08-03 11:49:58","msg":"AgentLogin","agentName":"1008","agentPwd":"1008","extNum":"","description":"坐席登陆"}
            //<agentMsgLog>{"msgType":"AgentRec","dateTime":"2022-08-03 11:49:58","msg":"AgentLoginOk","agentName":"1008","extNum":"1008","agentGroupId":"6703","description":"坐席登陆成功"}
            //<agentMsgLog>{"msgType":"PbxApi","dateTime":"2022-08-03 11:49:58","msg":"AgentUnPause","agentName":"1008","extNum":"1008","agentGroupId":"6703","description":"恢复队列服务(取消暂停)","cause":"坐席登陆 恢复队列服务(取消暂停)"}
            JSONObject jsonObj = JSONObject.parseObject(strMsgJson);//
    
            String strMsgType = jsonObj.get("msgType").toString();//消息类型
            String strMsgName = jsonObj.get("msg").toString();//消息名称
    
            if (strMsgType.equals("AgentSend") == true) {
                dbMySql_AgentSend thdAgentSend = new dbMySql_AgentSend(strMsgJson,strMsgType,strMsgName);
                thdAgentSend.start();
            }else if (strMsgType.equals("AgentRec") == true) {
                //logger.debug(msg);
                dbMySql_AgentRec thdAgentRec = new dbMySql_AgentRec(strMsgJson,strMsgType,strMsgName);
                thdAgentRec.start();
            } else if (strMsgType.equals("PbxApi") == true) {
                dbMySql_PbxApi thdPbxApi = new dbMySql_PbxApi(strMsgJson,strMsgType,strMsgName);
                thdPbxApi.start();
            }else if (strMsgType.equals("PbxEvent") == true) {
                dbMySql_PbxEvent thdPbxEvent = new dbMySql_PbxEvent(strMsgJson,strMsgType,strMsgName);
                thdPbxEvent.start();
            }
    
        }
    
        //region 读取配置文件
        public static Properties readConfigFile(String cfgFile) {
            try {
                //System.out.println(  JonCallCC.class.getClassLoader().getResource(cfgFile) + "--------------------");
                InputStream in = App.class.getClassLoader().getResource(cfgFile).openStream();
                Properties prop = new Properties();
                prop.load(in);
                return  prop;
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println( e.getMessage());
                return null;
            }
        }
        //endregion
    
        public static String getEncoding(String str) {
            String encode = "GB2312";
            try {
                if (isEncoding(str, encode)) { // 判断是不是GB2312
                    return encode;
                }
            } catch (Exception exception) {
            }
            encode = "ISO-8859-1";
            try {
                if (isEncoding(str, encode)) { // 判断是不是ISO-8859-1
                    return encode;
                }
            } catch (Exception exception1) {
            }
            encode = "UTF-8";
            try {
                if (isEncoding(str, encode)) { // 判断是不是UTF-8
                    return encode;
                }
            } catch (Exception exception2) {
            }
            encode = "GBK";
            try {
                if (isEncoding(str, encode)) { // 判断是不是GBK
                    return encode;
                }
            } catch (Exception exception3) {
            }
            return "如果都不是,说明输入的内容不属于常见的编码格式"; // 如果都不是,说明输入的内容不属于常见的编码格式。
        }
    
        public static boolean isEncoding(String str, String encode) {
            try {
                if (str.equals(new String(str.getBytes(), encode))) {
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("!!!!!!  isEncoding" + e.getMessage());
            }
            return false;
        }
    
    }

    测试结果

     点击交换机名称出现

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

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


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

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

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

  • 相关阅读:
    第七十天 how can I 坚持
    第六十九天 how can I 坚持
    第六十八天 how can I 坚持
    第六十七天 how can I 坚持 (补昨天)
    第六十六天 how can I 坚持··
    第六十五天 how can I 坚持
    第六十四天 how can i 坚持
    第六十三天 how can I 坚持
    MyEclipse10 中的两种FreeMarker插件的安装与配置
    画板社交工具开发分享——HTML5 canvas控件、PHP、社交分享学习(四)
  • 原文地址:https://www.cnblogs.com/hailexuexi/p/16576796.html
Copyright © 2020-2023  润新知