ZeroMQ模式
一、相关知识
1、基础API接口
创建和销毁套接字:zmq_socket(), zmq_close()
配置和读取套接字选项:zmq_setsockopt(), zmq_getsockopt()
为套接字建立连接:zmq_bind(), zmq_connect()
发送和接收消息:zmq_send(), zmq_recv()
2、ZMQ与TCP的区别
使用多种协议,inproc(进程内)、ipc(进程间)、tcp、pgm(广播)、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有自己的协议用来使上面的模式运作。