RabbitMq-发布订阅模式

本文记录了RabbitMQ的发布订阅模式的学习过程,包括生产者如何发送消息,以及两个不同的消费者如何订阅并接收这些消息,展示了发布订阅模式在消息传递中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习笔记:

生产者 

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Publish {

    //队列名称
    private static final String QUEUE_INFORM_EMAIL="queue_inform_email";
    private static final String QUEUE_INFORM_SMS="queue_inform_sms";
    private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";

    public static void main(String[] args) throws IOException, TimeoutException {

        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个mq
        connectionFactory.setVirtualHost("/");
        Connection connection=null;
        Channel channel=null;
        try {
            //建立连接
            connection=connectionFactory.newConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel中完成
            channel = connection.createChannel();

            //声明队列
            /**
             *  String queue,boolean durable,boolean exclusive,boolean autoDelete ,Map<String,Object> arguments</String,Object>
             *  参数明细
             *  1.队列名称
             *  2:是否持久化,如果持久化,mq重启队列还在
             *  3: exclusive :是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列自动删除。如果将此参数设置true可用于临时u队列的创建
             *  4:autoDelete:自动删除,队列不再使用时是否删除队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
             *  5:arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
             */
            channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
            channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);

            //声明一个交换机
            /**
             *  String exchange,String type;
             *  exchange :接交换机的名称
             *  type: 交换机的类型
             *  fanout: 对应的rabbitmq的工作模式是 publis/subscribe
             *  direct: 对应的Routing工作模式
             *  topic: 对应于Topics工作模式
             *  headers: 对应于headers工作模式
             */
            channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
            //进行交换机和队列进行绑定
            /**
             *  String queue,String exchange,String routingKey
             *  1 queue:队列名称
             *  2:exchange :交换机的名称
             *  3:routingKey: 路由key,作用是交换机根据路由key的值将消息转发到指定的队列中去
             *     在发布订阅模式中定义为空字符串
             */
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");
            channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,"");

            /**
             *  String exchange,String routingKey,BasicProperties,byte[] body
             *  1: exchange:交换机,如果不指定将使用mq的默认交换机
             *  2:routingKey: 路由key,交换机根据路由key来将消息转发到指定的队列,如果使用默认交换机,ruotingkey设置为默认的队列名称
             *  3:basicProperties: 消息的属性
             *  4:body:消息内容
             */
            String msg="hello 你好!";

            //发送多条消息
            for(int i=0;i<6;i++){
                //发送消息
                channel.basicPublish(EXCHANGE_FANOUT_INFORM,"",null,msg.getBytes());
                System.out.println("send to many users " +i);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }finally {

           //关闭连接
            //先关闭通道 通道在连接中
//            channel.close();
//            connection.close();
        }

    }
}

 

消费者1

 

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Consumer_subscribe_sms {

    //队列名称
    private static final String QUEUE_INFORM_SMS="queue_inform_sms";

    private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";

    public static void main(String[] args) throws IOException, TimeoutException {

        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个mq
        connectionFactory.setVirtualHost("/");
        Connection connection=null;
        Channel channel=null;
        //建立连接
        connection=connectionFactory.newConnection();
        //创建会话通道,生产者和mq服务所有通信都在channel中完成
        channel = connection.createChannel();
        //声明队列
        /**
         *  String queue,boolean durable,boolean exclusive,boolean autoDelete ,Map<String,Object> arguments</String,Object>
         *  参数明细
         *  1.队列名称
         *  2:是否持久化,如果持久化,mq重启队列还在
         *  3: exclusive :是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列自动删除。如果将此参数设置true可用于临时u队列的创建
         *  4:autoDelete:自动删除,队列不再使用时是否删除队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
         *  5:arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
         */
        channel.queueDeclare(QUEUE_INFORM_SMS,true,false,false,null);
        //声明一个交换机
        /**
         *  String exchange,String type;
         *  exchange :接交换机的名称
         *  type: 交换机的类型
         *  fanout: 对应的rabbitmq的工作模式是 publis/subscribe
         *  direct: 对应的Routing工作模式
         *  topic: 对应于Topics工作模式
         *  headers: 对应于headers工作模式
         */
        channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);

        //进行交换机和队列进行绑定
        /**
         *  String queue,String exchange,String routingKey
         *  1 queue:队列名称
         *  2:exchange :交换机的名称
         *  3:routingKey: 路由key,作用是交换机根据路由key的值将消息转发到指定的队列中去
         *     在发布订阅模式中定义为空字符串
         */
        channel.queueBind(QUEUE_INFORM_SMS,EXCHANGE_FANOUT_INFORM,"");

        //实现消费者方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){

            //到接受消息后,此方法会被调用
            /**
             *
             * @param consumerTag 消费者标签 用来标识消费者的,在监听队列时设置channel.basicConsume
             * @param envelope   信封 通过envelope
             * @param properties 消息属性
             * @param body 消息内容
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //交换机
                String exchange=envelope.getExchange();

                //消息id.mq在channel中用来标识消息的id,可用于确消已接收
                long deliveryTag = envelope.getDeliveryTag();

                //消息内容
                String s = new String(body, "utf-8");
                System.out.println("receive message:"+s);
            }
        };
        /**
         *  String queue,boolean autoAck,Consumer callback
         *  1:queue 队列名称
         *  2:autoAck 自动回复,当消费者接收到消息后要告诉mq消息一接收,如果将此参数设置为true表示会自动回复
         *             如果设置为 false 要通过编程实现回复
         *  3:callbacl 消费方法,当消费者接受到消息要执行的方法
         */
        //监听队列
        channel.basicConsume(QUEUE_INFORM_SMS,true,defaultConsumer);
    }
}

消费者2

import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_subscribe_email {
    //队列名称
    private static final String QUEUE_INFORM_EMAIL="queue_inform_email";

    private static final String EXCHANGE_FANOUT_INFORM="exchange_fanout_inform";

    public static void main(String[] args) throws IOException, TimeoutException {

            ConnectionFactory connectionFactory=new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("guest");
            connectionFactory.setPassword("guest");
            //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个mq
            connectionFactory.setVirtualHost("/");

            Connection connection=null;
            Channel channel=null;
            //建立连接
            connection=connectionFactory.newConnection();
            //创建会话通道,生产者和mq服务所有通信都在channel中完成
            channel = connection.createChannel();
            //声明队列
            /**
             *  String queue,boolean durable,boolean exclusive,boolean autoDelete ,Map<String,Object> arguments</String,Object>
             *  参数明细
             *  1.队列名称
             *  2:是否持久化,如果持久化,mq重启队列还在
             *  3: exclusive :是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列自动删除。如果将此参数设置true可用于临时u队列的创建
             *  4:autoDelete:自动删除,队列不再使用时是否删除队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
             *  5:arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
             */
            channel.queueDeclare(QUEUE_INFORM_EMAIL,true,false,false,null);
            //声明一个交换机
            /**
             *  String exchange,String type;
             *  exchange :接交换机的名称
             *  type: 交换机的类型
             *  fanout: 对应的rabbitmq的工作模式是 publis/subscribe
             *  direct: 对应的Routing工作模式
             *  topic: 对应于Topics工作模式
             *  headers: 对应于headers工作模式
             */
            channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
            //进行交换机和队列进行绑定
            /**
             *  String queue,String exchange,String routingKey
             *  1 queue:队列名称
             *  2:exchange :交换机的名称
             *  3:routingKey: 路由key,作用是交换机根据路由key的值将消息转发到指定的队列中去
             *     在发布订阅模式中定义为空字符串
             */
            channel.queueBind(QUEUE_INFORM_EMAIL,EXCHANGE_FANOUT_INFORM,"");

            //实现消费者方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){

                //到接受消息后,此方法会被调用
                /**
                 *
                 * @param consumerTag 消费者标签 用来标识消费者的,在监听队列时设置channel.basicConsume
                 * @param envelope   信封 通过envelope
                 * @param properties 消息属性
                 * @param body 消息内容
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //交换机
                    String exchange=envelope.getExchange();

                    //消息id.mq在channel中用来标识消息的id,可用于确消已接收
                    long deliveryTag = envelope.getDeliveryTag();

                    //消息内容
                    String s = new String(body, "utf-8");
                    System.out.println("receive message:"+s);
                }
            };
        /**
         *  String queue,boolean autoAck,Consumer callback
         *  1:queue 队列名称
         *  2:autoAck 自动回复,当消费者接收到消息后要告诉mq消息一接收,如果将此参数设置为true表示会自动回复
         *             如果设置为 false 要通过编程实现回复
         *  3:callbacl 消费方法,当消费者接受到消息要执行的方法
         */
        //监听队列
        channel.basicConsume(QUEUE_INFORM_EMAIL,true,defaultConsumer);
    }
}

 

 

 

 

 

 

 

 

 

 

      

 

      
 

 

        

     

<think>好的,用户想了解如何在RabbitMQ的C客户端(rabbitmq-c)中实现发布/订阅模式。我需要先回忆一下RabbitMQ的基本概念,特别是发布/订阅相关的机制。发布/订阅通常使用交换机,比如fanout类型,这样消息会被广播到所有绑定的队列。用户可能已经对RabbitMQ有一定了解,但需要具体的操作步骤。 首先,应该确定用户的操作系统环境,但rabbitmq-c是C语言的库,可能在Linux下使用较多。不过具体步骤可能大同小异。用户需要安装rabbitmq-c库,可能还需要安装amqp-tools或者其他依赖。这点可能需要提到,比如通过包管理器安装或者源码编译。 接下来,发布者和订阅者的实现步骤。发布者需要创建连接、通道,声明交换机,然后发布消息到交换机,而不是直接到队列。订阅者则需要声明队列,将队列绑定到交换机,然后开始消费消息。需要强调交换机的类型为fanout,这样所有绑定的队列都会收到消息。 代码结构方面,可能需要分开发布者和订阅者的示例。在C语言中,使用rabbitmq-c的API,需要注意内存管理和错误处理,比如检查每个函数的返回值,释放资源等。例如,amqp_connection_close和amqp_destroy_connection的区别,确保正确关闭连接。 另外,队列声明时,如果是订阅者,可能需要让服务器生成随机队列名,并设置自动删除属性,这样当消费者断开时队列会被删除。或者在示例中简化,使用固定的队列名,但可能在实际应用中需要动态生成。 需要提醒用户编译时链接正确的库,比如使用-lrabbitmq的编译选项。可能还需要包含头文件路径,比如-I后跟安装路径。 可能用户会遇到的问题,比如交换机未声明导致消息无法路由,或者队列未正确绑定。这时候可以建议检查错误码,或者使用RabbitMQ的管理界面查看交换机是否存在,绑定情况如何。 最后,是否应该提供完整的代码示例?虽然之前的回复已经有了,但可能需要更详细的注释,或者分步骤解释每个函数的作用,比如amqp_basic_publish的参数意义,特别是routing_key在fanout交换机下是否可以为空,或者是否需要指定。 另外,确认rabbitmq-c的版本是否支持某些特性,但一般来说,fanout是基本功能,应该没问题。但如果有兼容性问题,可能需要用户注意版本。 总结步骤:安装库,编写发布者(连接、创建通道、声明fanout交换机、发送消息),编写订阅者(连接、通道、声明队列、绑定到交换机、消费消息)。强调交换机类型和绑定过程,以及错误处理。</think>在 RabbitMQ 中使用 C 语言客户端库 `rabbitmq-c` 实现 **发布/订阅(Pub/Sub)模式**,需通过 **Fanout 类型交换机**将消息广播给所有绑定的队列。以下是操作步骤和示例代码: --- ### 一、环境准备 1. **安装 rabbitmq-c 库** - Ubuntu/Debian: ```bash sudo apt-get install librabbitmq-dev ``` - 源码编译: 从 [GitHub](https://github.com/alanxz/rabbitmq-c) 下载并编译。 --- ### 二、发布者(Publisher)流程 1. **创建连接和通道** 2. **声明 Fanout 类型交换机** 3. **发送消息到交换机** ```c #include <amqp.h> #include <amqp_tcp_socket.h> int main() { // 1. 建立连接 amqp_connection_state_t conn = amqp_new_connection(); amqp_socket_t *socket = amqp_tcp_socket_new(conn); amqp_socket_open(socket, "localhost", 5672); amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); // 2. 创建通道并声明 Fanout 交换机 amqp_channel_open(conn, 1); amqp_exchange_declare(conn, 1, amqp_cstring_bytes("logs"), amqp_cstring_bytes("fanout"), 0, 0, 0, 0, amqp_empty_table); // 3. 发布消息 amqp_basic_publish(conn, 1, amqp_cstring_bytes("logs"), amqp_cstring_bytes(""), // routing_key 留空 0, 0, NULL, amqp_cstring_bytes("Hello from C Publisher!")); // 清理资源 amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS); amqp_connection_close(conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(conn); return 0; } ``` --- ### 三、订阅者(Subscriber)流程 1. **创建连接和通道** 2. **声明匿名队列**(服务器生成唯一名称) 3. **将队列绑定到 Fanout 交换机** 4. **消费消息** ```c #include <amqp.h> #include <amqp_tcp_socket.h> #include <amqp_framing.h> int main() { // 1. 建立连接 amqp_connection_state_t conn = amqp_new_connection(); amqp_socket_t *socket = amqp_tcp_socket_new(conn); amqp_socket_open(socket, "localhost", 5672); amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); // 2. 创建通道并声明队列(匿名队列) amqp_channel_open(conn, 1); amqp_queue_declare_ok_t *queue = amqp_queue_declare( conn, 1, amqp_empty_bytes, 0, 0, 1, 1, amqp_empty_table); amqp_bytes_t queue_name = queue->queue; // 3. 绑定队列到 Fanout 交换机 amqp_queue_bind(conn, 1, queue_name, amqp_cstring_bytes("logs"), // 交换机名 amqp_empty_bytes, // routing_key 留空 amqp_empty_table); // 4. 开始消费消息 amqp_basic_consume(conn, 1, queue_name, amqp_empty_bytes, 0, 1, 0, amqp_empty_table); while (1) { amqp_envelope_t envelope; amqp_maybe_release_buffers(conn); amqp_consume_message(conn, &envelope, NULL, 0); printf("Received: %.*s\n", (int)envelope.message.body.len, (char *)envelope.message.body.bytes); amqp_destroy_envelope(&envelope); } // 清理资源(此处不可达,实际需处理信号终止循环) amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS); amqp_connection_close(conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(conn); return 0; } ``` --- ### 四、编译与运行 1. **编译命令**(需链接 `rabbitmq` 库): ```bash gcc publisher.c -o publisher -lrabbitmq gcc subscriber.c -o subscriber -lrabbitmq ``` 2. **启动服务**: - 确保 RabbitMQ 服务已运行(`systemctl start rabbitmq-server`)。 3. **测试效果**: - 启动多个订阅者,它们会收到发布者的每条消息。 --- ### 五、关键点说明 - **交换机类型**:必须使用 `fanout` 类型以实现广播。 - **匿名队列**:订阅者声明队列时,不指定名称(`amqp_empty_bytes`)让服务器生成唯一名称。 - **绑定路由键**:Fanout 交换机忽略路由键,订阅者的 `routing_key` 参数可留空。 - **持久化**:示例未配置持久化,实际生产环境需根据需求设置 `durable` 参数。 遇到问题时,可通过 RabbitMQ 管理界面(默认端口 15672)检查交换机绑定状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值