基于AMQP协议模拟MQTT的发布 / 订阅主题功能

1. MQTT 的主题模式

MQTT 的核心就是 发布 / 订阅主题

  • 客户端直接 PUBLISH 到一个主题(例如 sensor/temp)。
  • 其他客户端 SUBSCRIBE 同一个主题或通配符主题(例如 sensor/#)。
  • Broker 负责将消息推送给所有订阅了匹配主题的客户端。
  • 主题是轻量级的字符串,支持层级(/ 分隔)和通配符(+ 单层,# 多层)。
  • 不需要提前声明主题,客户端可以随时发布或订阅。

2. AMQP 的类似机制

AMQP 没有 "主题" 这个概念,但可以通过 交换机(Exchange)+ 队列(Queue)+ 绑定(Binding) 来模拟 MQTT 的主题效果:

1.使用 topic 类型交换机

  • 交换机类型topic

  • 路由键(Routing Key) :用点 . 分隔,类似 MQTT 的主题层级,例如:

    plaintext

    复制代码
    sensor.temp
    sensor.humidity
    device.control
  • 绑定键(Binding Key) :支持通配符:

    • * 匹配一个单词(层级)
    • # 匹配零个或多个单词(层级)

例子

  • 生产者发送消息到交换机 my_topic_exchange,路由键 sensor.temp
  • 队列 topic_queue 绑定到交换机,绑定键 sensor.# → 会收到所有 sensor.xxx 的消息。

3.交换机、队列和绑定关系的示意图

示意图说明

  • 交换机my_topic_exchange(类型 topic
  • 队列topic_queue
  • 绑定键sensor.#(只匹配以 sensor 开头的主题)
  • 生产者 发布不同主题的消息:
    • sensor.temp → 消费者收到
    • sensor.humidity → 消费者收到
    • device.control → 消费者不会收到 (因为绑定键只匹配 sensor 开头的主题)

4.Python程序

  1. setup_topic.py:负责初始化 RabbitMQ 的交换机、队列和绑定规则,是整个消息系统的基础。
  2. producer_topic.py:负责发送消息到交换机,每条消息可以指定不同的主题。
  3. consumer_topic.py :负责从队列中接收并处理消息,但只订阅特定主题(sensor 开头)。

1. setup_topic.py

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
setup_topic.py - 一次性创建 RabbitMQ Topic 交换机、队列和绑定关系

功能说明:
1. 连接到 RabbitMQ Broker
2. 创建 topic 类型交换机(持久化)
3. 创建队列(持久化)
4. 将队列绑定到交换机,只接收 sensor 开头的主题
"""

import pika

# ======================== 配置参数 ========================
HOST = 'localhost'              # RabbitMQ 服务器的 IP 地址
PORT = 5672                     # RabbitMQ 默认 AMQP 端口
USER = 'mi_user'                # 用户名
PASSWORD = 'YourPassword123'    # 密码
EXCHANGE_NAME = 'my_topic_exchange'  # 交换机名称
QUEUE_NAME = 'topic_queue'           # 队列名称
BINDING_KEY = 'sensor.#'              # 绑定键(只匹配 sensor 开头的主题)

try:
    # 1. 创建连接(BlockingConnection 表示同步阻塞模式)
    conn = pika.BlockingConnection(
        pika.ConnectionParameters(
            host=HOST,
            port=PORT,
            credentials=pika.PlainCredentials(USER, PASSWORD)  # 用户名密码认证
        )
    )

    # 2. 创建一个信道(Channel),所有操作都在信道中进行
    ch = conn.channel()
    print(f"✅ 连接成功 {HOST}:{PORT}")

    # 3. 创建 topic 交换机
    #    exchange_type='topic':支持通配符的主题交换机
    #    durable=True:交换机持久化,RabbitMQ 重启后仍然存在
    ch.exchange_declare(
        exchange=EXCHANGE_NAME,
        exchange_type='topic',
        durable=True
    )
    print(f"✅ 交换机 '{EXCHANGE_NAME}' 已创建")

    # 4. 创建队列
    #    durable=True:队列持久化,RabbitMQ 重启后仍然存在
    ch.queue_declare(queue=QUEUE_NAME, durable=True)
    print(f"✅ 队列 '{QUEUE_NAME}' 已创建")

    # 5. 绑定队列到交换机
    #    routing_key=BINDING_KEY:绑定键(交换机根据绑定键过滤消息)
    #    'sensor.#':匹配所有以 sensor 开头的主题(包括多层级)
    ch.queue_bind(
        exchange=EXCHANGE_NAME,
        queue=QUEUE_NAME,
        routing_key=BINDING_KEY
    )
    print(f"✅ 已绑定队列到交换机,绑定键: {BINDING_KEY}")

    # 6. 关闭连接
    conn.close()
    print("\n✅ 基础设施配置完成!")

except Exception as e:
    # 捕获并处理异常
    print(f"❌ 配置失败: {e}")

2. producer_topic.py

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
producer_topic.py - 发布消息到不同的主题

功能说明:
1. 连接到 RabbitMQ Broker
2. 使用 topic 交换机发布消息
3. 支持不同主题(路由键)
"""

import pika
import json

# ======================== 配置参数 ========================
HOST = 'localhost'              # RabbitMQ 服务器的 IP 地址
PORT = 5672                     # RabbitMQ 默认 AMQP 端口
USER = 'mi_user'                # 用户名
PASSWORD = 'YourPassword123'    # 密码
EXCHANGE_NAME = 'my_topic_exchange'  # 交换机名称

def send_to_topic(topic, message):
    """
    发送消息到指定主题(路由键)
    :param topic: 主题字符串,例如 "sensor.temp"
    :param message: 消息内容(Python 字典)
    """
    # 1. 创建连接
    conn = pika.BlockingConnection(
        pika.ConnectionParameters(
            host=HOST,
            port=PORT,
            credentials=pika.PlainCredentials(USER, PASSWORD)
        )
    )

    # 2. 创建信道
    ch = conn.channel()

    # 3. 声明 topic 交换机(确保存在)
    ch.exchange_declare(
        exchange=EXCHANGE_NAME,
        exchange_type='topic',
        durable=True
    )

    # 4. 发布消息到指定主题(路由键)
    ch.basic_publish(
        exchange=EXCHANGE_NAME,
        routing_key=topic,
        body=json.dumps(message),
        properties=pika.BasicProperties(delivery_mode=2)  # 持久化消息
    )

    print(f"✅ 发布到主题 [{topic}]: {message}")
    conn.close()

if __name__ == "__main__":
    # 模拟发布不同主题的消息
    send_to_topic("sensor.temp", {"value": 25.5, "unit": "C"})
    send_to_topic("sensor.humidity", {"value": 60, "unit": "%"})
    send_to_topic("device.control", {"cmd": "restart", "target": "sensor_001"})

3. consumer_topic.py

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
consumer_topic.py - 只订阅 sensor 开头的主题

功能说明:
1. 连接到 RabbitMQ Broker
2. 绑定队列到 topic 交换机,只接收 sensor 开头的主题
3. 消费消息并手动确认
"""

import pika
import json

# ======================== 配置参数 ========================
HOST = 'localhost'              # RabbitMQ 服务器的 IP 地址
PORT = 5672                     # RabbitMQ 默认 AMQP 端口
USER = 'mi_user'                # 用户名
PASSWORD = 'YourPassword123'    # 密码
EXCHANGE_NAME = 'my_topic_exchange'  # 交换机名称
QUEUE_NAME = 'topic_queue'           # 队列名称

def callback(ch, method, properties, body):
    """
    收到消息的回调函数
    :param ch: 信道对象
    :param method: 消息方法属性(包含 delivery_tag 等)
    :param properties: 消息属性
    :param body: 消息内容(二进制数据)
    """
    topic = method.routing_key  # 获取消息的主题(路由键)
    message = json.loads(body.decode('utf-8'))  # 将二进制数据转换为 Python 字典
    print(f"\n📨 收到主题 [{topic}]: {message}")
    ch.basic_ack(delivery_tag=method.delivery_tag)  # 手动确认消息已处理完成

def start_consumer():
    """
    启动消费者,只订阅 sensor 开头的主题
    """
    # 1. 创建连接
    conn = pika.BlockingConnection(
        pika.ConnectionParameters(
            host=HOST,
            port=PORT,
            credentials=pika.PlainCredentials(USER, PASSWORD)
        )
    )

    # 2. 创建信道
    ch = conn.channel()

    # 3. 声明 topic 交换机(确保存在)
    ch.exchange_declare(
        exchange=EXCHANGE_NAME,
        exchange_type='topic',
        durable=True
    )

    # 4. 声明队列(确保存在)
    ch.queue_declare(queue=QUEUE_NAME, durable=True)

    # 5. 绑定队列到交换机,只接收 sensor 开头的主题
    ch.queue_bind(
        exchange=EXCHANGE_NAME,
        queue=QUEUE_NAME,
        routing_key='sensor.#'  # 绑定键(只匹配 sensor 开头的主题)
    )

    # 6. 设置每次只处理一条消息(公平分发)
    ch.basic_qos(prefetch_count=1)

    # 7. 开始消费消息
    ch.basic_consume(queue=QUEUE_NAME, on_message_callback=callback, auto_ack=False)

    print(f"🔄 开始订阅 sensor 开头的主题 (按 Ctrl+C 退出)")
    ch.start_consuming()

if __name__ == "__main__":
    start_consumer()
相关推荐
ZhuNian的学习乐园1 天前
LLM对齐核心:RLHF 从基础到实践全解析
人工智能·python·算法
编程饭碗1 天前
【Java 类的完整组成】
java·开发语言·python
DLite1 天前
Python静态类型设计:语法割裂的槽点
开发语言·python
2501_921649491 天前
如何获取外汇实时数据:全球货币行情对接指南
后端·python·websocket·金融·区块链
时光Autistic1 天前
【环境配置】安装LaTeX并配置到PyCharm使用
ide·python·pycharm·latex
岁岁的O泡奶1 天前
NSSCTF_crypto_[LitCTF 2024]common_primes
开发语言·python·算法
韩师傅1 天前
从随叫随到到规范配送:现代物流系统与 REST API 的登场
后端·python·全栈
阿拉丁的梦1 天前
五种翻译--mo字典翻译任何blender插件的插件
python·blender
玄同7651 天前
Python 系统编程双雄:sys 与 os 模块深度实践指南
开发语言·数据库·人工智能·windows·笔记·python·microsoft