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

posted @ 2022-08-11 17:15  海乐学习  阅读(366)  评论(0编辑  收藏  举报