第二种模式是数据分发,即服务端推送消息到一组客户端,让我们看一下天气预报是如何推送天气信息,其
信息内容包括地区,温度和湿度等信息,我们将生成随机数据,如天气信息:
这是server端程序,我们使用5556这个端口
// Weather update server
// Binds PUB socket to tcp://*:5556
// Publishes random weather updates
#include "zhelpers.h"
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);
s_send (publisher, update);
}
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 "zhelpers.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
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;
for (update_nbr = 0; update_nbr < 100; update_nbr++) {
char *string = s_recv (subscriber);
int zipcode, temperature, relhumidity;
sscanf (string, "%d %d %d",
&zipcode, &temperature, &relhumidity);
total_temp += temperature;
free (string);
}
printf ("Average temperature for zipcode '%s' was %dF\n",
filter, (int) (total_temp / update_nbr));
zmq_close (subscriber);
zmq_ctx_destroy (context);
return 0;
}
注意当你使用SUB套接字的时候,你必须使用zmq_setsockopt()和SUBSCRIBE进行订阅设置,
如果你不进行此设置,你将一无所获;对于新手,这是一个经常犯的错误;
订阅者可以设置很多订阅,如果一个更新信息匹配任何一个,订阅者就会收到该消息;订阅者
也可以取消某个订阅,订阅信息不一定是一个可见的字符串,可以参考zmq_setsockopt()来
看看他是如何工作的;
PUB-SUB模式是异步的,客户端循环调用zmq_recv(),如果试图发送一个消息给SUB套接口,则
会导致一个错误;类似的,服务端会根据需要调用zmq_send(),但是不一定会在PUB套接口上调用zmq_recv;
理论上,使用zeromq套接字,不用关心哪一侧连接,哪一侧绑定;实际上也并没有什么不同;
所以,绑定PUB,连接SUB,除非你的网络设计这种方式行不通;
对于PUB-SUB模式来说,还有一件重要的事情,你无法预测何时订阅者会接收消息,即使你启动
一个订阅者, 等待一会,然后启动发布者,订阅者总会丢失发布者发送的第一个消息,这是因为
当订阅者连接到发布者时(这会消耗点时间),发布者可能已将消息发送出去;
这种 "slow joiner"我们之后会详细讨论,记住一点,zeromq是异步io,也就是说,你有两个
节点干这件事情,按找这个顺序:
订阅者连接到一个端点,接收和计数消息
发布者绑定到一个端点,并立即发送1000个消息
然后订阅者极有可能没有收到消息,或许你会确定下正确的设置并继续尝试,订阅者依旧未能
收到消息;
tcp的握手会消耗几毫秒,这依赖于你的网络以及两个端点之间的跳数,这时,zeromq可以发送
好多消息;假定,建立连接会耗费5毫秒,那么每秒钟将处理1M个消息,在这5毫秒中,订阅者
会连接发布者,发布者会每1毫秒发送1K个消息;
在第二章中,我们将讨论如何同步订阅者和发布者,以便直到待订阅者准备好,发布者才发布数据;
不过,延迟发布是一件很蠢的事情,在应用程序中,不要这样做,这是很不雅的很脆弱的,
并且低效的,到第二章我们会知道如何处理是正确的;
订阅者接收其区域的100个数据包,这就意味着从服务器发送10M个消息包,如果是随机发布的话;
你可以先启动客户端,然后再启动服务端,客户端会继续工作;你可以随心所欲的启动服务端,客户端
也会照常工作,当客户端收集够100个消息包的时候,就会计算打印这些数据,然后退出;
关于订阅-发布模式
一个订阅者可以每次使用一个连接来连接多个发布者,数据通道是双向的,所以发布者
不会互相干扰;
如果发布者没有订阅者连接,他会丢弃所有的消息;
如果你使用tcp,订阅者会比较慢,消息会在发布端依次排序,后续我们会看到如何使用“高水位”
来保护发布者;
在zeromq的3.x版本中,当使用(tcp:// or ipc://)协议时,发布端会进行过滤;使用epgm://
协议,订阅端会进行过滤;在 ZeroMQ v2.x版本中on个,两侧都会进行过滤;
这是在我笔记本中测试的接收和过滤10M消息的耗时,配置为 Intel i5
$ time wuclient
Collecting updates from weather server...
Average temperature for zipcode '10001 ' was 28F
real 0m4.470s
user 0m0.000s
sys 0m0.008s