Linux下使用ZMQ实践“发布-订阅”模型

一、背景

    继续ZMQ系列,本期我们看一下“单生产者-多消费者”的编程场景,使用ZMQ_PUB/ZMQ_SUB实践“发布-订阅”模型

二、相关知识

2.1 ZMQ_PUB

ZMQ_PUB
    A socket of type ZMQ_PUB is used by a publisher to distribute data. Messages sent are distributed in a fan out fashion to all connected peers. The
    zmq_recv(3) function is not implemented for this socket type.

    When a ZMQ_PUB socket enters the mute state due to having reached the high water mark for a subscriber, then any messages that would be sent to the
    subscriber in question shall instead be dropped until the mute state ends. The zmq_send() function shall never block for this socket type.

ZMQ_PUB为发布端socket类型,用于消息分发,消息以扇出的方式分发到各个连接端上。该socket类型仅支持zmq_send进行发送,不支持zmq_recv()。注意当订阅者处理速度慢的时候,需要在PUB设置合适的高水位HWM来保证消息不会丢失。

2.2 ZMQ_SUB

ZMQ_SUB
    A socket of type ZMQ_SUB is used by a subscriber to subscribe to data distributed by a publisher. Initially a ZMQ_SUB socket is not subscribed to any
    messages, use the ZMQ_SUBSCRIBE option of zmq_setsockopt(3) to specify which messages to subscribe to. The zmq_send() function is not implemented for
    this socket type.

ZMQ_SUB为订阅端socket类型,用于订阅接收发布者发送的消息。需要通过设置 ZMQ_SUBSCRIBE 选项来指定订阅哪种消息。该socket不支持zmq_send()方法。 

2.3 使用方法

使用方法为一个PUB对应多个SUB,如图所示:

Linux下使用ZMQ实践“发布-订阅”模型_第1张图片

三、实现

Publisher代码如下,绑定在127.0.0.1:5443 上进行监听

void test_pub(void *ctx, int times)
{
        int ret = 0, ix = 0;
        char id[16] = {0};
        char request[1024];

        void *sock = zmq_socket(ctx, ZMQ_PUB);
        assert(sock);

        s_set_id_ex(sock, id, sizeof(id));

        ret = zmq_bind(sock, "tcp://127.0.0.1:5443");
        assert(ret == 0);

        LOGN("Pub %s start\n", id);
        for (ix = 0; ix < times; ix++) {
                snprintf(request, sizeof(request), "Data-%03s-%03d", id, ix);
                s_send(sock, request);
                LOGN("Pub %s send: %s\n", id, request);
                usleep(300 * 1000);
        }
        LOGN("Pub %s stop\n", id);

        zmq_close(sock);
}

Subscriber则进行地址连接,进行接收:

int test_sub(void *ctx)
{
        int ret = 0, cnt = 0;
        char id[16] = {0};
        char request[1024];

        void *sock = zmq_socket(ctx, ZMQ_SUB);
        assert(sock);

        s_set_id_ex(sock, id, sizeof(id));
        ret = zmq_connect(sock, "tcp://127.0.0.1:5443");
        assert(ret == 0);

        ret = zmq_setsockopt(sock, ZMQ_SUBSCRIBE, "", 0);
        assert(ret == 0);

        LOGN("Sub %s start\n", id);
        while (++cnt) {
                s_recv(sock, request);
                LOGN("Sub %s recv: %s\n", id, request);
                usleep(300 * 1000);
        }
        LOGN("Sub %s stop\n", id);

        zmq_close(sock);
}

主函数入口:

int main(int argc, char *argv[])
{
        void *ctx = zmq_ctx_new();
        assert(ctx);

        srandom(time(NULL));

        if (argc > 1) {
                test_pub(ctx, atoi(argv[1]));
        }
        else {
                test_sub(ctx);
        }
        zmq_ctx_destroy(ctx);
        exit(EXIT_SUCCESS);
}

开启2个Subscriber、1个Publisher,执行结果:

Subscriber#1、Subscriber#2 先启动后阻塞住,开启Publisher后才收到消息:

./pubsub 
[ 1528395731.593 ]: Sub 0034 start
[ 1528395745.306 ]: Sub 0034 recv: Data-00B9-001
[ 1528395745.607 ]: Sub 0034 recv: Data-00B9-002
[ 1528395745.907 ]: Sub 0034 recv: Data-00B9-003
[ 1528395746.209 ]: Sub 0034 recv: Data-00B9-004
[ 1528395746.509 ]: Sub 0034 recv: Data-00B9-005
[ 1528395746.810 ]: Sub 0034 recv: Data-00B9-006
[ 1528395747.111 ]: Sub 0034 recv: Data-00B9-007
[ 1528395747.413 ]: Sub 0034 recv: Data-00B9-008
[ 1528395747.714 ]: Sub 0034 recv: Data-00B9-009
./pubsub 
[ 1528395738.672 ]: Sub 00B7 start
[ 1528395745.306 ]: Sub 00B7 recv: Data-00B9-001
[ 1528395745.607 ]: Sub 00B7 recv: Data-00B9-002
[ 1528395745.907 ]: Sub 00B7 recv: Data-00B9-003
[ 1528395746.209 ]: Sub 00B7 recv: Data-00B9-004
[ 1528395746.509 ]: Sub 00B7 recv: Data-00B9-005
[ 1528395746.810 ]: Sub 00B7 recv: Data-00B9-006
[ 1528395747.111 ]: Sub 00B7 recv: Data-00B9-007
[ 1528395747.413 ]: Sub 00B7 recv: Data-00B9-008
[ 1528395747.714 ]: Sub 00B7 recv: Data-00B9-009

Publisher:

./pubsub 10 
[ 1528395745.004 ]: Pub 00B9 start
[ 1528395745.004 ]: Pub 00B9 send: Data-00B9-000
[ 1528395745.306 ]: Pub 00B9 send: Data-00B9-001
[ 1528395745.606 ]: Pub 00B9 send: Data-00B9-002
[ 1528395745.907 ]: Pub 00B9 send: Data-00B9-003
[ 1528395746.208 ]: Pub 00B9 send: Data-00B9-004
[ 1528395746.509 ]: Pub 00B9 send: Data-00B9-005
[ 1528395746.809 ]: Pub 00B9 send: Data-00B9-006
[ 1528395747.111 ]: Pub 00B9 send: Data-00B9-007
[ 1528395747.412 ]: Pub 00B9 send: Data-00B9-008
[ 1528395747.714 ]: Pub 00B9 send: Data-00B9-009
[ 1528395748.014 ]: Pub 00B9 stop
netstat -anpt|grep pubsub
tcp        0      0 127.0.0.1:5443          0.0.0.0:*               LISTEN      31141/pubsub        
tcp        0      0 127.0.0.1:36680         127.0.0.1:5443          ESTABLISHED 31074/pubsub        
tcp        0      0 127.0.0.1:36678         127.0.0.1:5443          ESTABLISHED 31071/pubsub        
tcp        0      0 127.0.0.1:5443          127.0.0.1:36680         ESTABLISHED 31141/pubsub        
tcp        0      0 127.0.0.1:5443          127.0.0.1:36678         ESTABLISHED 31141/pubsub
期间netstat查看连接状态,发现Publisher启动后,两个Subscriber才建立起socket连接;

四、结论

    本文实践了ZeroMQ的 PUB/SUB模式,该模式可以实现一对多的消息分发功能。在实际场景中使用,需要根据订阅者的数量、消息大小进行HWM设置,来保证消息可靠性、内存使用情况。


参考文章:

[1] http://zguide.zeromq.org/page:all#toc49

[2] https://blog.csdn.net/yahohi/article/details/76231389


你可能感兴趣的:(linux,socket)