通过之前对ZeroMQ有个基本了解后,我们了解一下关于sockets的以下两点:
首先我们了解一下第二经典模式,publish-subscribe模式,该方式可以通过服务端发送一系列消息给客户端子集。是一种一对多的模型。
基本的意图是publisher发送一条消息 ,subscribers接受消息,断开连接的subscribers将丢弃消息。publisher和subscribers松耦合的,并不关心是否有subscribers存在。
就好像是TV频道或者电台工作方式。TV频道总是广播电视节目,只有打开该频道的观看者才可以接受到。如果你错误了时间,你将错过你喜爱的节目。publish-subscribe模式的优点是提供更加动态的网络拓扑。
publish-subscribe模式可以总结为以下主要点:
我们来看个例子吧。考虑我们要开发一个股票交易的程序。存有经纪人,他们想知道有多少股票。我们股票是publisher,而subscriber是经济人.
我们不实际去拿股票信息,我们随机一些随机股票值
server.c
/*
* Stock Market Server
* Binds PUB socket to tcp://*:4040
* Publishes random stock values of random companies
*/
#include <string.h>
#include "zmq.h"
int main(int argc, char *argv[])
{
void *context = zmq_ctx_new();
void *publisher = zmq_socket(context, ZMQ_PUB);
printf("Starting server...\n");
int conn = zmq_bind(publisher, "tcp://*:4040");
const char *companies[2] = {"Company1", "Company2"};
int count = 0;
for (;;) {
int price = count % 2;
int which_company = count % 2;
int index = strlen(companies[0]);
char update[12];
snprintf(update, sizeof(update), "%s", companies[which_company]);
zmq_msg_t message;
zmq_msg_init_size(&message, index);
memcpy(zmq_msg_data(&message), update, index);
zmq_msg_send(&message, publisher, 0);
zmq_msg_close(&message);
count++;
}
zmq_close(publisher);
zmq_ctx_destroy(context);
}
client.c
/*
* Stock Market Client
* Connects PUB socket to tcp://localhost:4040
* Collects stock exchange values
*/
#include <stdlib.h>
#include <string.h>
#include "zmq.h"
int main(int argc, char *argv[])
{
void *context = zmq_ctx_new();
void *subscriber = zmq_socket(context, ZMQ_SUB);
printf("Collecting stock information from the server.\n");
int conn = zmq_connect(subscriber, "tcp://localhost:4040");
conn = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, 0, 0);
int i;
for (i = 0; i < 10; i++) {
zmq_msg_t reply;
zmq_msg_init(&reply);
zmq_msg_recv(&reply, subscriber, 0);
int length = zmq_msg_size(&reply);
char* value = malloc(length+1);
memcpy(value, zmq_msg_data(&reply), length);
zmq_msg_close(&reply);
value[length] = '\0';
printf("%s\n", value);
free(value);
}
zmq_close(subscriber);
zmq_ctx_destroy(context);
return 0;
}
使用SUB socket时,必须使用zmq_setsockopt()设置subscribe为subscription,否则将无法收到任何消息。这是一个很常见的错误。
subscriber可以设置多个subscriptions,也可以单个取消subscription。
subscriber使用zmq_msg_recv()来接收消息。zmq_msg_recv()从socket取得消息,并存储消息。之前的消息将会销毁。
int zmq_msg_recv (zmq_msg_t *msg, void *socket, int flags);
flags只能设置为一个值,ZMQ_DONTWAIT.如果指定ZMQ_DONTWAIT,那么操作执行为non-blocking模式。如果消息成功接受,zmq_msg_recv()返回消息大小(bytes),否则返回-1,以及错误消息flag。
publish-subscribe模式是异步的,发送一个消息到SUB socket来触发错误。你可以使用zmq_msg_send()发送任何消息,但是你不能使用zmq_msg_recv()在PUB socket上进行调用。
我们看看输出:
$ ./client
Collecting stock information from the server.
Company2
Company1
Company2
Company1
Company2
Company1
Company2
Company1
Company2
Company1
publisher在没有subscriber的情况下仍然会发送消息。
$ ./server
Starting server...
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
我们假设只想取得Company1或者传入的参数的消息。
/*
* Stock Market Client
* Connects PUB socket to tcp://localhost:4040
* Collects stock exchange values
*/
#include <stdlib.h>
#include <string.h>
#include "zmq.h"
int main(int argc, char *argv[])
{
void *context = zmq_ctx_new();
void *subscriber = zmq_socket(context, ZMQ_SUB);
const char* filter;
if (argc > 1) {
filter = argv[1];
} else {
filter = "Company1";
}
printf("Collecting stock information from the server.\n");
int conn = zmq_connect(subscriber, "tcp://localhost:4040");
conn = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, filter, strlen(filter));
int i;
for (i = 0; i < 10; i++) {
zmq_msg_t reply;
zmq_msg_init(&reply);
zmq_msg_recv(&reply, subscriber, 0);
int length = zmq_msg_size(&reply);
char* value = malloc(length+1);
memcpy(value, zmq_msg_data(&reply), length);
zmq_msg_close(&reply);
value[length] = '\0';
printf("%s\n", value);
free(value);
}
zmq_close(subscriber);
zmq_ctx_destroy(context);
return 0;
}
$ ./client2
Collecting stock information from the server.
Company1
Company1
Company1
Company1
Company1
Company1
Company1
Company1
Company1
Company1
我们的股票交易应用发送消息给subscribers。看起来好像程序正常工作了,是吗?悲哀,不是。使用SUB socket时,必须使用zmq_setsockopt()设置subscribe为subscription,否则将无法收到任何消息。这是一个很常见的错误。
subscriber可以设置多个subscriptions,也可以单个取消subscription。
subscriber使用zmq_msg_recv()来接收消息。zmq_msg_recv()从socket取得消息,并存储消息。之前的消息将会销毁。
int zmq_msg_recv (zmq_msg_t *msg, void *socket, int flags);
flags只能设置为一个值,ZMQ_DONTWAIT.如果指定ZMQ_DONTWAIT,那么操作执行为non-blocking模式。如果消息成功接受,zmq_msg_recv()返回消息大小(bytes),否则返回-1,以及错误消息flag。
publish-subscribe模式是异步的,发送一个消息到SUB socket来触发错误。你可以使用zmq_msg_send()发送任何消息,但是你不能使用zmq_msg_recv()在PUB socket上进行调用。
我们看看输出:
$ ./client
Collecting stock information from the server.
Company2
Company1
Company2
Company1
Company2
Company1
Company2
Company1
Company2
Company1
publisher在没有subscriber的情况下仍然会发送消息。
$ ./server
Starting server...
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
Sending: Company1
Sending: Company2
我们假设只想取得Company1或者传入的参数的消息。
/*
* Stock Market Client
* Connects PUB socket to tcp://localhost:4040
* Collects stock exchange values
*/
#include <stdlib.h>
#include <string.h>
#include "zmq.h"
int main(int argc, char *argv[])
{
void *context = zmq_ctx_new();
void *subscriber = zmq_socket(context, ZMQ_SUB);
const char* filter;
if (argc > 1) {
filter = argv[1];
} else {
filter = "Company1";
}
printf("Collecting stock information from the server.\n");
int conn = zmq_connect(subscriber, "tcp://localhost:4040");
conn = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, filter, strlen(filter));
int i;
for (i = 0; i < 10; i++) {
zmq_msg_t reply;
zmq_msg_init(&reply);
zmq_msg_recv(&reply, subscriber, 0);
int length = zmq_msg_size(&reply);
char* value = malloc(length+1);
memcpy(value, zmq_msg_data(&reply), length);
zmq_msg_close(&reply);
value[length] = '\0';
printf("%s\n", value);
free(value);
}
zmq_close(subscriber);
zmq_ctx_destroy(context);
return 0;
}
$ ./client2
Collecting stock information from the server.
Company1
Company1
Company1
Company1
Company1
Company1
Company1
Company1
Company1
Company1
我们的股票交易应用发送消息给subscribers。看起来好像程序正常工作了,是吗?悲哀,不是。
ZeroMQ通过使用prefix来匹配内容,意味着ZeroMQ将仅返回Company1, Company10, 以及Company101。
我们来修改一下代码:
/*
* Stock Market Server
* Binds PUB socket to tcp://*:4040
* Publishes random stock values of random companies
*/
#include <string.h>
#include "zmq.h"
int main(int argc, char *argv[])
{
void *context = zmq_ctx_new();
void *publisher = zmq_socket(context, ZMQ_PUB);
printf("Starting server...\n");
int conn = zmq_bind(publisher, "tcp://*:4040");
const char *companies[3] = {"Company1", "Company10", "Company101"};
int count = 0;
for (;;) {
int which_company = count % 3;
int index = strlen(companies[which_company]);
char update[64];
snprintf(update, sizeof(update), "%s", companies[which_company]);
zmq_msg_t message;
zmq_msg_init_size(&message, index);
printf("Sending: %s\n", update);
memcpy(zmq_msg_data(&message), update, index);
zmq_msg_send(&message, publisher, 0);
zmq_msg_close(&message);
count++;
}
zmq_close(publisher);
zmq_ctx_destroy(context);
return 0;
}
在修改subscriber为如下:
/*
* Stock Market Client
* Connects PUB socket to tcp://localhost:4040
* Collects stock exchange values
*/
#include <stdlib.h>
#include <string.h>
#include "zmq.h"
int main(int argc, char *argv[])
{
void *context = zmq_ctx_new();
void *subscriber = zmq_socket(context, ZMQ_SUB);
const char* filter;
if (argc > 1) {
filter = argv[1];
} else {
filter = "Company1";
}
printf("Collecting stock information from the server.\n");
int conn = zmq_connect(subscriber, "tcp://localhost:4040");
conn = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, filter, strlen(filter));
int i;
for (i = 0; i < 10; i++) {
zmq_msg_t reply;
zmq_msg_init(&reply);
zmq_msg_recv(&reply, subscriber, 0);
int length = zmq_msg_size(&reply);
char* value = malloc(length+1);
memcpy(value, zmq_msg_data(&reply), length);
zmq_msg_close(&reply);
printf("msg: %s, length: %d\n", value, length);
free(value);
}
zmq_close(subscriber);
zmq_ctx_destroy(context);
return 0;
}
输出结果如下:
$ ./client3
Collecting stock information from the server.
msg: Company1, length: 8
msg: Company10, length: 9
msg: Company101, length: 10
msg: Company101, length: 8
msg: Company101, length: 9
msg: Company101, length: 10
msg: Company101, length: 8
msg: Company101, length: 9
msg: Company101, length: 10
msg: Company101, length: 8
我们的subscriber设置filter为Company1.但是publisher也给了我们Company10以及Company101。这不是我们想要的结果。
最简单的方式是使用delimiter
publisher
/*
* Stock Market Server
* Binds PUB socket to tcp://*:4040
* Publishes random stock values of random companies
*/
#include <stdlib.h>
#include <string.h>
#include "zmq.h"
int main(int argc, char *argv[])
{
void *context = zmq_ctx_new();
void *publisher = zmq_socket(context, ZMQ_PUB);
printf("Starting server...\n");
int conn = zmq_bind(publisher, "tcp://*:4040");
const char *companies[3] = {"Company1", "Company10", "Company101"};
int count = 0;
for (;;) {
int price = count % 17;
int which_company = count % 3;
char update[64];
sprintf(update, "%s| %d", companies[which_company], price);
int index = strlen(update);
zmq_msg_t message;
zmq_msg_init_size(&message, index);
printf("Sending: %s\n", update);
memcpy(zmq_msg_data(&message), update, index);
zmq_msg_send(&message, publisher, 0);
zmq_msg_close(&message);
count++;
}
zmq_close(publisher);
zmq_ctx_destroy(context);
return 0;
}
subscriber
/*
* Stock Market Client
* Connects PUB socket to tcp://localhost:4040
* Collects stock exchange values
*/
#include <stdlib.h>
#include <string.h>
#include "zmq.h"
int main(int argc, char *argv[])
{
void *context = zmq_ctx_new();
void *subscriber = zmq_socket(context, ZMQ_SUB);
const char* filter;
filter = "Company1|";
printf("Collecting stock information from the server.\n");
int conn = zmq_connect(subscriber, "tcp://localhost:4040");
conn = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, filter, strlen(filter));
int i;
for (i = 0; i < 10; i++) {
zmq_msg_t reply;
zmq_msg_init(&reply);
zmq_msg_recv(&reply, subscriber, 0);
int length = zmq_msg_size(&reply);
char* value = malloc(length+1);
memcpy(value, zmq_msg_data(&reply), length);
zmq_msg_close(&reply);
printf("msg: %s, length: %d\n", value, length);
free(value);
}
zmq_close(subscriber);
zmq_ctx_destroy(context);
return 0;
}
我们就得到了想要的结果
$ ./client4
Collecting stock information from the server.
msg: Company1| 12, length: 12
msg: Company1| 15, length: 12
msg: Company1| 15, length: 11
msg: Company1| 45, length: 11
msg: Company1| 75, length: 11
msg: Company1| 10, length: 12
msg: Company1| 13, length: 12
msg: Company1| 16, length: 12
msg: Company1| 26, length: 11
msg: Company1| 56, length: 11
在publish-subscribe模式中,我们使用了ZMQ_SUBSCRIBE选项
conn = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, option_value,strlen(option_value));
socket选项通过zmq_setsockopt()函数设置,接受的参数为:
选项长度
int zmq_setsockopt (void socket, int option_name, const void option_value, size_t option_len);
ZMQ_SUBSCRIBE实现接受ZMQ_SUB中接收消息,如果option_value不为空,那么只订阅option_value开头的所有消息。你可以给一个ZMQ_SUB socket添加多个过滤器
ZMQ_UNSUBSCRIBE从ZMQ_SUB socket中移除消息。如果有多个过滤器,仅移除一个。
一个要注意的事项是publisher-subscriber socket中我们不知道subscriber何时收到消息。本例中,最好的办法是先启动subscriber然后启动publisher.因为subscriber总是会错过第一条消息,因为连接publisher需要时间,而那时publisher有可能已经发送了一条消息。
我们会讨论如何同步publisher和subscribers。我们会在subscribers没有连上不发送任何消息。
我们接着了解一下pipeline模式, pipline模式通过pipline有序传输数据到节点。数据持续不断传输,每步pipeline连接一个或多个节点。round-robin策略被用来节点中传输数据。和request-reply模式相像。
在编程过程逃不了divide和conquer策略。
http://en.wikipedia.org/wiki/Divide_and_conquer_algorithms
我们来在ZeroMQ中执行一些并行任务。考虑一个场景,我们有一个producer生成一些随机数字,我们有workers通过牛顿方法计算这些数的平方根。然后我们有collector来搜集这么workers的结果。
以下是服务端代码:
#include <stdio.h>
#include <stdlib.h>
#include <unstd.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include "zmq.h"
int main(int argc, char* argv[])
{
void* context = zmq_ctx_new();
// This is the socket that we send message
void* socket = zmq_socket(context, ZMQ_PUSH);
zmq_bind(socket, "tcp://*:4040");
// This is the socket that we send batch message
void* connector = zmq_socket(context, ZMQ_PUSH);
zmq_connect(connector, "tcp://localhost:5050");
printf("Please press enter when workers are ready...");
getchar();
printf("Sending tasks to workers...\n");
// The first message. It's also the signal start of batch.
int length = strlen("-1");
zmq_msg_t message;
zmq_msg_init_size(&message, length);
memcpy(zmq_msg_data(&message), "-1", length);
zmq_msg_send(&message, connector, 0);
zmq_msg_close(&message);
// Generate some random numbers;
srandom((unsigned) time(NULL));
// Send the tasks.
int count;
int msec = 0;
for (count = 0; count < 100; count++) {
int load = (int) ((double)(100) * random() / RAND_MAX);
msec += load;
char string[10];
sprintf(string, "%d", load);
}
printf("Total: %d msec\n", msec);
sleep(1);
zmq_close(connector);
zmq_close(socket);
zmq_ctx_destroy(context);
return 0;
}
以下是worker代码:
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "zmq.h"
double square(double x)
{
return x * x;
}
double average(double x, double y)
{
return (x + y) / 2.0;
}
double good_enough(double guess, double x)
{
return abs(square(guess) - x) < 0.000001;
}
double improve(double guess, double x)
{
return average(guess, x / guess);
}
double sqrt_inner(double guess, double x)
{
if (good_enough(guess, x))
return guess;
else
return sqrt_inner(improve(guess, x), x);
}
double newton_sqrt(double x) {
return sqrt_inner(1.0, x);
}
int main(int arc, char* argv[])
{
void* context = zmq_ctx_new();
// Let's initialize a socket to receive messages
void* receiver = zmq_socket(context, ZMQ_PULL);
zmq_connect(receiver, "tcp://localhost:4040");
// Let's initialize a socket to send the messages.
void* sender = zmq_socket(context, ZMQ_PUSH);
zmq_connect(sender, "tcp://localhost:5050");
for(;;) {
zmq_msg_t reply;
zmq_msg_init(&reply);
zmq_msg_recv(&reply, receiver, 0);
int length = zmq_msg_size(&reply);
char *msg = malloc(length+1);
memcpy(msg, zmq_msg_data(&reply), length);
zmq_msg_close(&reply);
fflush(stdout);
double val = atof(msg);
printf("%.1f: %.1f\n", value, newton_sqrt(val));
sleep(1);
free(msg);
zmq_msg_t message;
char* ssend = "T";
int t_length = strlen(ssend);
zmq_msg_init_size(&messge, t_length);
memcpy(zmq_msg_data(&message), ssend, t_length);
zmq_msg_send(&message, sender, 0);
zmq_msg_close(&message);
}
zmq_close(receiver);
zmq_close(sender);
zmq_ctx_detroy(context);
return 0;
}
reciver代码:
#include <stdlib.h>
#include <string.h>
#include "zmq.h"
int main(int arc, char* argv[])
{
void* context = zmq_ctx_new();
void* receiver = zmq_socket(context, ZMQ_PULL);
zmq_bind(receiver, "tcp://*:5050");
// We receiver the first message and discard it since it's the
// signal start of batch which is -1.
zmq_msg_t reply;
zmq_msg_init(&reply);
zmq_msg_recv(&reply, receiver, 0);
int length = zmq_msg_size(&reply);
char *msg = malloc(length+1);
memcpy(msg, zmq_msg_data(&reply), length);
zmq_msg_close(&reply);
free(msg);
int count;
for(count = 0; count < 100; count++) {
zmq_msg_t reply;
zmq_msg_init(&reply);
zmq_msg_recv(&reply, receiver, 0);
int length = zmq_msg_size(&reply);
char *value = malloc(length+1);
memcpy(value, zmq_msg_data(&reply), length);
zmq_msg_close(&reply);
free(value);
if (cout / 10 == 0)
printf("10 Tasks have been processed.\n");
fflush(stdout);
}
zmq_close(receiver);
zmq_ctx_detroy(context);
return 0;
}
做了啥:
我们来深入了解一下上下游关系:
// Let's initialize a socket to receive messages.
void* receiver = zmq_socket(context, ZMQ_PULL);
zmq_connect(receiver, "tcp://localhost:4040");
当我们想从上游节点获取数据时使用ZMQ_PULL.ZMQ_PULL类型sockets在pipeline中获取上游节点消息,就如早期所提及,该工作fair-queue调度。
注意:zmq_send()不能用在ZMQ_PULL
当我们想要于下游节点通信时使用ZMQ_PUSH.ZMQ_PUSH类型sockets用来在pipline中发送消息到下游。
ZMQ_PUSH从不丢弃消息,直到下游节点收到消息之前zmq_send()操作都会阻塞。
你一定意识到我们所有的示例都是从zmq_ctx_new()开始,ZeroMQ应用总是从先创建context开始。所有的sockets都应该使用contex放入单个进程中.ZeroMQ context是线程安全,你可以共享在多个线程中。
注意:如果ZeroMQ context无法创建,将会返回NULL。
虽然可以创建多个context,建议仅仅只创建一个。
在应用程序末,你需要销毁创建的context,使用zmq_ctx_destroy()。一旦调用了zmq_ctx_destroy(),所有进程将返回错误码(ETERM),并且zmq_ctx_destroy()将在成功使用zmq_close()之前阻塞。
当你在使用例如Python, Java 语言时,你无法担心内存管理,因为这些语言内置了清理内存机制。
不过使用C语言的时候,内存管理就属于你的责任了。以下是在结束应用程序后需要考虑的事情: