前言
ZMQ(ØMQ、ZeroMQ, 0MQ)看起来像是一套嵌入式的网络链接库,但工作起来更像是一个并发式的框架。它提供的套接字可以在多种协议中传输消息,如线程间、进程间、TCP、广播等。你可以使用套接字构建多对多的连接模式,如扇出、发布-订阅、任务分发、请求-应答等。ZMQ的快速足以胜任集群应用产品。它的异步I/O机制让你能够构建多核应用程序,完成异步消息处理任务。ZMQ有着多语言支持,并能在几乎所有的操作系统上运行。ZMQ是iMatix公司的产品,以LGPL开源协议发布。
zmq本体是用C++实现的,提供C语言头文件。https://github.com/zeromq/libzmq
网站https://zeromq.org/get-started/的其它实现是对zmq接口的封装,需要依赖zmq库。
一、提问-回答 模式
client通过REQ socket发送Hello,server通过REP socket接收Hello并返回World
此例中,bind放在了server端,connect在client端,在zmq中其位置可以互换。server和client程序启动顺序无关,client先启动后会阻塞等待server启动。
- 一个进程只需要一个zmq context就可以了
- 一个zmq socket可多次bind
- zmq socket有严格的配对关系,只有特定类型的socket之间才可通讯 https://libzmq.readthedocs.io/en/latest/zmq_socket.html
- bind 和 connect是对等关系,位置可以互换
// Hello World server
#include <zmq.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
int main (void)
{
// Socket to talk to clients
void *context = zmq_ctx_new ();
void *responder = zmq_socket (context, ZMQ_REP);
int rc = zmq_bind (responder, "tcp://*:5555");
assert (rc == 0);
// 同一个socket可多次bind
rc = zmq_bind(responder, "tcp://*:60000");
assert(rc == 0);
rc = zmq_bind(responder, "ipc://socket_type");
assert(rc == 0);
while (1) {
char buffer [10];
zmq_recv (responder, buffer, 10, 0);
printf ("Received Hello\n");
sleep (1); // Do some 'work'
zmq_send (responder, "World", 5, 0);
}
zmq_close(responder);
zmq_ctx_destroy(context);
return 0;
}
// Hello World client
#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
int main (void)
{
printf ("Connecting to hello world server...\n");
void *context = zmq_ctx_new ();
void *requester = zmq_socket (context, ZMQ_REQ);
zmq_connect (requester, "tcp://localhost:5555");
int request_nbr;
for (request_nbr = 0; request_nbr != 10; request_nbr++) {
char buffer [10];
printf ("Sending Hello %d...\n", request_nbr);
zmq_send (requester, "Hello", 5, 0);
zmq_recv (requester, buffer, 10, 0);
printf ("Received World %d\n", request_nbr);
}
zmq_close (requester);
zmq_ctx_destroy (context);
return 0;
}
client也可connect多次,具体内部实现不详
二、发布-订阅 模式
如下图,ZMQ 的 PUB-SUB是一对多的模式:
- bind一个发送方,多个接收方可接到相同的消息。即便没有sub,pub也一直再发。
- 可通过zmq_setsockopt设置多个filter,但如果不设置zmq_setsockopt则收不到任何数据。如果filter=“”,则接收所有数据
- ZMQ_SUB通过filter匹配接收信息的前缀
- ZMQ_PUB只能zmq_send; ZMQ_SUB只能zmq_recv
// Weather update server
// Binds PUB socket to tcp://*:5556
// Publishes random weather updates
#include "zmq.h"
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <time.h>
#include <stdlib.h>
#define randof(num) (int)((float)(num)*random()/(RAND_MAX+1.0))
int main (void)
{
// Prepare our context and publisher
void *context = zmq_ctx_new ();
void *publisher = zmq_socket (context, ZMQ_PUB);
int rc = zmq_bind (publisher, "tcp://*:5556");
assert (rc == 0);
// Initialize random number generator
srandom ((unsigned) time (NULL));
while (1) {
// Get values that will fool the boss
int zipcode, temperature, relhumidity;
zipcode = randof (100000);
temperature = randof (215) - 80;
relhumidity = randof (50) + 10;
// Send message to all subscribers
char update [20];
sprintf (update, "%05d %d %d", zipcode, temperature, relhumidity);
zmq_send(publisher, update, sizeof(update), 0);
}
zmq_close (publisher);
zmq_ctx_destroy (context);
return 0;
}
// Weather update client
// Connects SUB socket to tcp://localhost:5556
// Collects weather updates and finds avg temp in zipcode
#include "zmq.h"
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <time.h>
#include <stdlib.h>
int main (int argc, char *argv [])
{
// Socket to talk to server
printf ("Collecting updates from weather server...\n");
void *context = zmq_ctx_new ();
void *subscriber = zmq_socket (context, ZMQ_SUB);
int rc = zmq_connect (subscriber, "tcp://localhost:5556");
assert (rc == 0);
// Subscribe to zipcode, default is NYC, 10001
const char *filter = (argc > 1)? argv [1]: "10001 ";
rc = zmq_setsockopt (subscriber, ZMQ_SUBSCRIBE,
filter, strlen (filter));
assert (rc == 0);
// Process 100 updates
int update_nbr;
long total_temp = 0;
char *string = (char*)malloc(BUFSIZ);
for (update_nbr = 0; update_nbr < 100; update_nbr++) {
int rx_size = zmq_recv(subscriber, string, BUFSIZ, 0);
assert(rx_size >= 0);
string[rx_size] = '\0';
int zipcode, temperature, relhumidity;
sscanf (string, "%d %d %d",
&zipcode, &temperature, &relhumidity);
total_temp += temperature;
}
printf ("Average temperature for zipcode '%s' was %dF\n",
filter, (int) (total_temp / update_nbr));
zmq_close (subscriber);
zmq_ctx_destroy (context);
return 0;
}
三、Divide and Conquer
- ventilator产生可被并行处理的任务
- 一系列的worker可以用于处理任务
- sink收集worker的结果
现实中workers运行在超快的设备上,也许是在GPUs上解数学运算。
关于zmq context
- 一个进程只需有一个zmq context实例即可,如果有两个则会分离zmq实例。技术上说它是个容器。
- 如果使用fork,在子进程的开头调用一次zmq_ctx_new()。
- 使用zmq_send/recv可避免 直接使用zmq_msg_t 对象
- 使用zmq_msg_recv就需要zmq_msgclose
- zmq_ctx_new; zmq_socket; zmq_close; zmq_ctx_destroy;
- First, do not try to use the same socket from multiple threads. Please don’t explain why you think this would be excellent fun, just please don’t do it.
基础用法
利用zmq_msg_t,通过zmq_msg_recv接收并获取、处理数据,最后通过zmq_send发送
zmq_msg_t message;
zmq_msg_init(&message);
int size = zmq_msg_recv(&message, response, 0);
char* str = (char*)malloc(size + 100);
memcpy(str, zmq_msg_data(&message), size);
static int num = 0;
snprintf(str+size, 100, " servid: %d, response: %d", pid, num++);
zmq_send(response, str, strlen(str), 0);
free(str);
zmq_msg_close(&message);
ref
If you copy/paste a lot of code, you’re going to copy/paste errors, too.
https://github.com/zeromq/libzmq
ZGUIDE: https://zguide.zeromq.org/docs/chapter1/
https://wizardforcel.gitbooks.io/zmq-guide/content/chapter1.html