继续ZMQ系列,本期我们看一下“单生产者-多消费者”的编程场景,使用ZMQ_PUB/ZMQ_SUB实践“发布-订阅”模型
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来保证消息不会丢失。
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()方法。
使用方法为一个PUB对应多个SUB,如图所示:
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