ZMQ-发布订阅模式

ZeroMQ(ZMQ)是一个高性能异步消息库,适用于构建可伸缩的分布式或并发应用。发布/订阅(PUB/SUB)模式是ZMQ支持的消息传递模式之一,在该模式下,发布者(Publisher)发送消息,订阅者(Subscriber)接收消息。订阅者可以订阅特定的消息主题,只有与其订阅匹配的消息会被接收。

发布/订阅模式的特性

  1. 一对多:一个发布者可以有多个订阅者,所有订阅者都会收到发布者发送的消息。
  2. 过滤消息:订阅者可以订阅特定的主题,只接收与主题匹配的消息。
  3. 异步通信:发布者和订阅者不需要同步操作,发布者可以继续发送消息,而不必等待订阅者接收。
  4. 松耦合:发布者和订阅者彼此独立,彼此不知道对方的存在,这使得系统更具可扩展性和灵活性。

使用场景

  1. 实时数据分发:股票行情、天气预报等需要将实时数据分发给多个接收者。
  2. 日志收集:应用程序可以将日志发送到集中式日志服务器,多个日志分析工具可以订阅这些日志。
  3. 通知系统:发布者发送通知消息,多个订阅者接收并处理这些通知,如消息推送、事件驱动的系统等。

C语言实现

下面是一个简单的C语言示例,演示了如何使用ZMQ的发布/订阅模式。这个示例包括一个发布者和一个订阅者。

安装ZeroMQ

在开始编写代码之前,请确保已安装ZeroMQ库。可以使用以下命令进行安装:

sudo apt-get install libzmq3-dev

发布者代码(publisher.c)

#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    // 创建新的ZMQ上下文
    void *context = zmq_ctx_new();

    // 创建发布者套接字
    void *publisher = zmq_socket(context, ZMQ_PUB);
    zmq_bind(publisher, "tcp://*:5555");

    while (1) {
        // 构建消息
        char message[20];
        sprintf(message, "Hello %d", rand() % 100);

        // 发送消息
        zmq_send(publisher, message, strlen(message), 0);
        printf("Sent: %s\n", message);
        sleep(1);
    }

    // 清理
    zmq_close(publisher);
    zmq_ctx_destroy(context);

    return 0;
}

订阅者代码(subscriber.c)

#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    // 创建新的ZMQ上下文
    void *context = zmq_ctx_new();

    // 创建订阅者套接字
    void *subscriber = zmq_socket(context, ZMQ_SUB);
    zmq_connect(subscriber, "tcp://localhost:5555");

    // 订阅所有消息(空字符串表示订阅所有主题)
    zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, "", 0);

    while (1) {
        char buffer[20];
        // 接收消息
        zmq_recv(subscriber, buffer, 20, 0);
        buffer[19] = '\0'; // 确保字符串以'\0'结尾
        printf("Received: %s\n", buffer);
    }

    // 清理
    zmq_close(subscriber);
    zmq_ctx_destroy(context);

    return 0;
}

编译和运行

使用以下命令编译和运行发布者和订阅者代码:

gcc -o publisher publisher.c -lzmq
gcc -o subscriber subscriber.c -lzmq

./publisher
./subscriber

首先运行publisher,然后在另一个终端中运行subscriber。你会看到发布者发送的消息被订阅者接收并打印出来。

以上示例展示了ZMQ发布/订阅模式的基本用法。通过这种模式,可以实现高效的消息分发和接收,适用于各种实时数据分发和事件通知的应用场景。

订阅指定Topic信息

在ZeroMQ的发布/订阅模式中,过滤消息是通过在订阅者设置订阅主题来实现的。发布者发送的消息可以包含主题信息,订阅者通过设置过滤规则来决定接收哪些消息。订阅者只会接收与其订阅主题匹配的消息。

如何实现消息过滤

1. 发布者代码

发布者需要在发送消息时包含主题信息。主题和消息之间用空格分隔。以下是修改后的发布者代码示例:

#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    // 创建新的ZMQ上下文
    void *context = zmq_ctx_new();

    // 创建发布者套接字
    void *publisher = zmq_socket(context, ZMQ_PUB);
    zmq_bind(publisher, "tcp://*:5555");

    while (1) {
        // 构建消息
        char message[30];
        char *topic = "news";
        sprintf(message, "%s %d", topic, rand() % 100);

        // 发送消息
        zmq_send(publisher, message, strlen(message), 0);
        printf("Sent: %s\n", message);
        sleep(1);
    }

    // 清理
    zmq_close(publisher);
    zmq_ctx_destroy(context);

    return 0;
}

在这个示例中,每条消息都以一个主题开头,后面跟着消息内容。主题和消息之间用空格分隔。

2. 订阅者代码

订阅者通过设置订阅主题来过滤消息。订阅者可以选择只接收特定主题的消息。以下是修改后的订阅者代码示例,其中订阅者选择只接收以"news"为主题的消息:

#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    // 创建新的ZMQ上下文
    void *context = zmq_ctx_new();

    // 创建订阅者套接字
    void *subscriber = zmq_socket(context, ZMQ_SUB);
    zmq_connect(subscriber, "tcp://localhost:5555");

    // 订阅特定的主题(例如“news”)
    char *subscription_topic = "news";
    zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, subscription_topic, strlen(subscription_topic));

    while (1) {
        char buffer[30];
        // 接收消息
        zmq_recv(subscriber, buffer, 30, 0);
        buffer[29] = '\0'; // 确保字符串以'\0'结尾
        printf("Received: %s\n", buffer);
    }

    // 清理
    zmq_close(subscriber);
    zmq_ctx_destroy(context);

    return 0;
}

在这个示例中,订阅者使用zmq_setsockopt函数设置订阅主题。只接收以"news"为主题的消息。

运行多个订阅者

你可以运行多个订阅者,每个订阅者可以设置不同的主题来接收不同类型的消息。例如,你可以创建一个订阅者来接收"news"主题的消息,另一个订阅者来接收其他主题的消息。可以通过在zmq_setsockopt中设置不同的主题来实现。

示例

  1. 运行发布者:

    ./publisher
    
  2. 运行第一个订阅者(接收"news"主题的消息):

    ./subscriber
    
  3. 如果需要测试其他主题,修改发布者代码中的主题内容,并重新编译,然后运行另一个订阅者实例进行测试。

这样,你就能实现消息的过滤,订阅者只会接收符合其订阅主题的消息。

Topic存在问题

在ZeroMQ的发布/订阅模式中,订阅者的主题匹配是基于消息的前缀进行的。具体来说,订阅者只会接收以其订阅主题为前缀的消息。这意味着如果一个订阅者订阅了news主题,它将接收到所有以news开头的消息,包括news2这样的消息,因为news2news的前缀范围内。

解释

当你订阅了news主题时,ZeroMQ 实际上将这个订阅主题作为前缀匹配的模式处理。因此:

  • 如果发布者发送的消息是news 123,这个消息将匹配news主题的订阅者。
  • 如果发布者发送的消息是news2 456,这个消息同样也会被news主题的订阅者接收到,因为news2news为前缀。

解决方案

如果你希望订阅者只接收确切匹配的主题,你可以使用更精确的匹配模式。ZeroMQ并不直接支持完全匹配的主题,但有几个方法可以达到类似的效果:

  1. 精确匹配

    你可以在消息的内容中添加分隔符或特定格式,使得订阅者能够区分不同的消息。例如,使用特殊的字符作为分隔符,确保每个消息的主题都是唯一且清晰的。

  2. 实现更复杂的过滤

    你可以在订阅者端进行更复杂的消息过滤。即使订阅者接收到所有匹配的消息,它也可以在接收消息后进一步解析和过滤,只处理符合特定条件的消息。

修改后的示例代码

发布者代码(publisher.c)

为了使主题更加明确,你可以在消息中使用更具体的格式:

#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    // 创建新的ZMQ上下文
    void *context = zmq_ctx_new();

    // 创建发布者套接字
    void *publisher = zmq_socket(context, ZMQ_PUB);
    zmq_bind(publisher, "tcp://*:5555");

    while (1) {
        // 发布"news"主题的消息
        char message[30];
        snprintf(message, sizeof(message), "news: %d", rand() % 100);
        zmq_send(publisher, message, strlen(message), 0);
        printf("Sent: %s\n", message);

        // 发布"news2"主题的消息
        snprintf(message, sizeof(message), "news2: %d", rand() % 100);
        zmq_send(publisher, message, strlen(message), 0);
        printf("Sent: %s\n", message);

        sleep(1);  // 暂停1秒
    }

    // 清理
    zmq_close(publisher);
    zmq_ctx_destroy(context);

    return 0;
}
订阅者代码(subscriber.c)

你可以在订阅者端进一步处理消息,确保只处理确切匹配的主题:

#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    // 创建新的ZMQ上下文
    void *context = zmq_ctx_new();

    // 创建订阅者套接字
    void *subscriber = zmq_socket(context, ZMQ_SUB);
    zmq_connect(subscriber, "tcp://localhost:5555");

    // 订阅"news"主题
    char *subscription_topic = "news:";
    zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, subscription_topic, strlen(subscription_topic));

    while (1) {
        char buffer[30];
        // 接收消息
        zmq_recv(subscriber, buffer, sizeof(buffer) - 1, 0);
        buffer[sizeof(buffer) - 1] = '\0'; // 确保字符串以'\0'结尾
        
        // 处理消息(仅处理以"news:"开头的消息)
        if (strncmp(buffer, "news:", 5) == 0) {
            printf("Received: %s\n", buffer);
        } else {
            printf("Ignored: %s\n", buffer);
        }
    }

    // 清理
    zmq_close(subscriber);
    zmq_ctx_destroy(context);

    return 0;
}

运行和测试

  1. 编译代码:

    gcc -o publisher publisher.c -lzmq
    gcc -o subscriber subscriber.c -lzmq
    
  2. 运行发布者:

    ./publisher
    
  3. 运行订阅者(例如订阅news:主题):

    ./subscriber
    

通过这些修改,你可以确保每个订阅者只处理特定主题的消息,并避免不需要的消息被接收。

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ZMQ是一个高效的消息传递库,支持多种传输协议,包括TCP、UDP、inproc等。它的广泛应用领域包括分布式系统、高性能计算、实时数据处理等。 ZMQ提供了多种通信模式,其中发布订阅模式是其中之一,用于多个订阅者同时订阅同一个主题的消息。下面是使用ZMQ实现发布订阅模式的Python代码示例。 首先,需要安装ZMQ库。可以使用pip命令进行安装: ``` pip install pyzmq ``` 然后,在发布者端,需要创建一个ZMQ的上下文对象,用于创建socket。然后,创建一个PUB类型的socket,并绑定到指定的地址和端口: ```python import zmq context = zmq.Context() socket = context.socket(zmq.PUB) socket.bind("tcp://*:5555") ``` 在订阅者端,也需要创建一个ZMQ的上下文对象,用于创建socket。然后,创建一个SUB类型的socket,并连接到发布者的地址和端口: ```python import zmq context = zmq.Context() socket = context.socket(zmq.SUB) socket.connect("tcp://localhost:5555") ``` 订阅者还需要指定要订阅的主题,可以使用socket的subscribe方法来实现: ```python topic = "news" socket.subscribe(topic.encode()) ``` 发布者可以使用socket的send方法来发送消息,并指定消息的主题: ```python topic = "news" message = "latest news" socket.send_multipart([topic.encode(), message.encode()]) ``` 订阅者可以使用socket的recv方法来接收消息: ```python message = socket.recv_multipart() topic = message[0].decode() content = message[1].decode() print(f"Received message: {content} under topic {topic}") ``` 完整的示例代码如下: Publisher: ```python import zmq context = zmq.Context() socket = context.socket(zmq.PUB) socket.bind("tcp://*:5555") while True: topic = input("Enter topic: ") message = input("Enter message: ") socket.send_multipart([topic.encode(), message.encode()]) ``` Subscriber: ```python import zmq context = zmq.Context() socket = context.socket(zmq.SUB) socket.connect("tcp://localhost:5555") topic = input("Enter topic to subscribe: ") socket.subscribe(topic.encode()) while True: message = socket.recv_multipart() topic = message[0].decode() content = message[1].decode() print(f"Received message: {content} under topic {topic}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值