ZeroMQ模式

不打扰是莪最后的温柔 提交于 2020-02-17 17:37:28

一、相关知识

1、基础API接口

创建和销毁套接字:zmq_socket(), zmq_close()
配置和读取套接字选项:zmq_setsockopt(), zmq_getsockopt()
为套接字建立连接:zmq_bind(), zmq_connect()
发送和接收消息:zmq_send(), zmq_recv()

2、ZMQ与TCP的区别

使用多种协议,inproc(进程内)ipc(进程间)tcppgm(广播)epgm
当客户端使用zmq_connect()时连接就已经建立了,并不要求该端点已有某个服务使用zmq_bind()进行了绑定;
连接是异步的,并由一组消息队列做缓冲;
连接会表现出某种消息模式,这是由创建连接的套接字类型决定的;
一个套接字可以有多个输入和输出连接;
ZMQ没有提供类似zmq_accept()的函数,因为当套接字绑定至端点时它就自动开始接受连接了;
应用程序无法直接和这些连接打交道,因为它们是被封装在ZMQ底层的。

3、ZMQ核心消息模型

请求-应答模式:将一组服务端和一组客户端相连,用于远程过程调用或任务分发。
发布-订阅模式 :将一组发布者和一组订阅者相连,用于数据分发。
管道模式:使用扇入或扇出的形式组装多个节点,可以产生多个步骤或循环,用于构建并行处理架构。
排他对接模式 :将两个套接字一对一地连接起来,这种模式应用场景很少。

二、基本套接字

  • REQ
  • REP
  • DEALER
  • ROUTER
  • PUB
  • SUB
  • PAIR

这里所说的套接字和BSD的套接字不是一个概念。一般来说BSD套接字是两个节点的链接。而zeromq套接字是一对多的链接。并且它们各有自己的规则。可以进行多种组合。

PUB - SUB
REQ - REP
REQ - ROUTER
DEALER - REP
DEALER - ROUTER
DEALER - DEALER
ROUTER - ROUTER
PUSH - PULL
PAIR - PAIR

ZMQ是通过后台的I/O线程进行消息传输的。在使用ZMQ之前,必须要创建一个ZMQ上下文。

通过函数:zmq_ctx_new()以及zmq_init()都可以创建一个ZMQ上下文,创建完上下文之后,才可以创建ZMQ的套接字。

销毁ZMQ上下文的时候,可以使用zmq_ctx_destroy()来销毁,需要注意的是,当ZMQ上下文中创建的套接字还未被全部关闭的时候,zmq_ctx_destroy()将会阻塞。

1、REQ-REP模式

请求回复模型,REQ请求然后等待响应。REQ监听然后回复。

REQ方先发后收,send-recv。REP方先收后发,recv-send。REQ和REP不停的重复它们的操作循环。REP类似于一个http服务器,REQ类似于客户端。一个REP可以连接多个REQ端,REP顺序处理REQ的请求。

描述一个场景用来解释请求回复模式的运作。想象有很多人在玩抛接球的游戏,游戏的一方是REQ另一方是REP。REQ是多方,REP只有一个。REQ轮流将球抛给REP,然后REP回传球。轮流代表不能同时进行,REP要完整的做完一次接和抛的动作才能进行下一个。

如果你看zeromq代码会发现REQ是DEALER的子类,REP是ROUTER的子类。

请求端:request.c

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


int main(void)
{
        void *ctx,*sock;
        int ret = 0;
        char data[1024];
        ctx = zmq_ctx_new();
        sock = zmq_socket(ctx,ZMQ_REQ);
        ret = zmq_connect(sock,"tcp://127.0.0.1:5555");
        while(1)
        {
                if(ret = zmq_send(sock,"hello",5,0)<0)
                        printf("REQ : zmq_send faild");
                sleep(3);
                bzero(data,sizeof(data)-1);
                if(ret = zmq_recv(sock,data,sizeof(data)-1,0)<0)
                        printf("REQ : zmq_recv faild");
                printf("REQ : recv msg %s\n",data);
        }
        zmq_close(sock);
        zmq_ctx_destroy(ctx);
        return 0;
}
编译:gcc request request.c -lzmq

应答端:response.c

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


int main(void)
{
        void *ctx,*sock;
        int ret = 0;
        char data[1024];
        ctx = zmq_ctx_new();
        sock = zmq_socket(ctx,ZMQ_REP);
        ret = zmq_bind(sock,"tcp://127.0.0.1:5555");
        while(1)
        {
                bzero(data,sizeof(data)-1);
                if(ret = zmq_recv(sock,data,sizeof(data)-1,0)<0)
                        printf("REP : zmq_recv faild");
                sleep(3);
                printf("REP : recv msg %s\n",data);
                memcpy(data,"world",5);
                if(ret = zmq_send(sock,data,5,0)<0)
                        printf("REP : zmq_send faild");
        }
        zmq_close(sock);
        zmq_ctx_destroy(ctx);
        return 0;
}
编译:gcc -o response response.c -lzmq

运行结果:
在这里插入图片描述

2、PUSH-PULL模式

推拉模式,PUSH发送,send。PULL方接收,recv。PUSH可以和多个PULL建立连接,PUSH发送的数据被顺序发送给PULL方。比如你PUSH和三个PULL建立连接,分别是A,B,C。PUSH发送的第一数据会给A,第二数据会给B,第三个数据给C,第四个数据给A。一直这么循环。

这个类似现实中的发牌,PUSH是发牌方,PUSH顺序给每个PULL发牌。顺序代表发完一个发另一个,不能同时进行。

管道模型:
由三部分组成,push进行数据推送,work进行数据缓存,pull进行数据竞争获取处理。区别于Publish-Subscribe存在一个数据缓存和处理负载。

当连接被断开,数据不会丢失,重连后数据继续发送到对端。

在这里插入图片描述

  • 最上面是产生任务的 分发者 ventilator
  • 中间是执行者 worker
  • 下面是收集结果的接收者 sink

服务端:push.c

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


int main(void)
{
        void *ctx,*sock;
        int ret = 0;
        char data[1024];
        int i = 0;
        ctx = zmq_ctx_new();
        sock = zmq_socket(ctx,ZMQ_PUSH);
        ret = zmq_bind(sock,"tcp://127.0.0.1:5555");
        while(1)
        {
                sprintf(data,"[%d]PUSH: Hello World",i++);
                ret = zmq_send(sock,data,strlen(data),0);
                sleep(3);
        }
        zmq_close(sock);
        zmq_ctx_destroy(ctx);
        return 0;
}

客户端:pull.c

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


int main(void)
{
        void *ctx,*sock;
        int ret = 0;
        char data[1024];
        ctx = zmq_ctx_new();
        sock = zmq_socket(ctx,ZMQ_PULL);
        ret = zmq_connect(sock,"tcp://127.0.0.1:5555");
        while(1)
        {
                bzero(data,sizeof(data));
                if(ret = zmq_recv(sock,data,sizeof(data)-1,0)<0)
                        printf("PULL : zmq_recv faild");
                printf("PULL:recv msg : %s\n",data);
                sleep(2);
        }
        zmq_close(sock);
        zmq__ctx_destroy(ctx);
        return 0;
}

运行结果:
在这里插入图片描述

3、PUB-SUB模式

发布订阅模式。PUB发送,send。SUB接收,recv。和PUSH-PULL模式不同,PUB将消息同时发给和他建立的链接,类似于广播。另外发布订阅模式也可以使用订阅过滤来实现只接收特定的消息。订阅过滤是在服务器上进行过滤的,如果一个订阅者设定了过滤,那么发布者将只发布满足他订阅条件的消息。

这个就是广播和收听的关系。PUB-SUB模式虽然没有使用网络的广播功能,但是它内部是异步的。也就是一次发送没有结束立刻开始下一次发送。

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

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

发布端:publish.c

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


int main(void)
{
        void *ctx,*sock;
        int ret = 0;
        char data[1024];
        int i = 0;
        ctx = zmq_ctx_new();
        sock = zmq_socket(ctx,ZMQ_PUB);
        ret = zmq_bind(sock,"tcp://127.0.0.1:5555");
        while(1)
        {
                sprintf(data,"[%d]PUB: Hello World",i++);
                ret = zmq_send(sock,data,strlen(data),0);
                sleep(2);
        }
        zmq_close(sock);
        zmq_ctx_destroy(ctx);
        return 0;
}

订阅端:subscribe.c

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


int main(void)
{
        void *ctx,*sock;
        int ret = 0;
        char data[1024];
        ctx = zmq_ctx_new();
        sock = zmq_socket(ctx,ZMQ_SUB);
        zmq_setsockopt(sock,ZMQ_SUBSCRIBE,"",0);
        ret = zmq_connect(sock,"tcp://127.0.0.1:5555");
        while(1)
        {
                bzero(data,sizeof(data));
                if(ret = zmq_recv(sock,data,sizeof(data)-1,0)<0)
                        printf("SUB : zmq_recv faild");
                printf("SUB:recv msg : %s\n",data);
                sleep(2);
        }
        zmq_close(sock);
        zmq_ctx_destroy(ctx);
        return 0;
}

运行结果:
在这里插入图片描述

4、DEALER-ROUTER模式

代理模式,这种模式主要用于扩容REQ-REP模式的。如果你需要扩容多个REP服务器。那么就可以用代理模式。ROUTER对应于REQ,它相当于一个REP服务器,执行先收后发,recv-send循环。但是内部它将前端REQ的请求收进来,然后将请求发给DEALER,DEALER接收ROUTER的消息,将消息发送给REP,只不过DEALER面对的是N个REP。通过代理模式的改造,REQ-REP将具有自动扩容能力。

DEALER-ROUTER模式都是异步的,如果将这个机制整合在一起运作。类似于一个异步的接线员,是一个N对接线员对M的链接拓扑关系。它将N个REQ请求链接到M个REQ上。并且保证它们是负载均衡的。

ZMQ_ROUTER类型的套接字是请求/回复模式的一种升级。

当ZMQ_ROUTER收到一个消息的时候,会自动在消息前面添加一帧,这一帧用来识别发送端的地址。

当发送一个消息的时候,需要先发送一帧对端的地址,然后再发送消息,如果目的地址指向的对端不存在了,这个消息就会被丢弃。

对端的地址默认情况下由ZMQ来产生一个唯一标识UUID。

5、PAIR-PAIR模式

配对模式,主要用于inproc进行进程内的通信。你可以在一个线程中调用recv等在哪里,另一个进程使用send来让接收线程继续。

这种模式类型与信号灯。

结语

zeromq使用模式的组合来完成网络通信任务和扩容任务。同时隐藏了复杂的网络编程细节,比如失败重连,补发消息,跨协议的桥接等复杂的网络编程。另外zeromq为你在不同的操作系统提供一个统一的编程界面,也简化了移植问题。

某些zeromq模式中的收发顺序模式,既不能打乱也不能省略。比如你不能用SUB进行发送将引发错误。REP模式也不能省略recv-send中的任何一个,不能跳过recv直接进行send,这都将引发错误。

另外zeromq不能为你实现其他的协议,比如你不能用zeromq实现一个HTTP服务器,zeromq有自己的传输协议,或者说zeromq有自己的协议用来使上面的模式运作。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!