上一篇好像就只是说了发布订阅模型,这一篇要把剩下的讲完,不过应该可以讲完,加油。
24.1 管道模型
管道模式用于将数据分布到分布在管道中的节点。数据总是沿着管道向下流动,管道的每个阶段都连接到至少一个节点。当一个管道阶段连接到多个节点时,所有连接节点之间的数据是循环的。
24.1.1 ZMQ_PUSH/ZMQ_PULL
看着这个模型真的有点像推流,其实我到现在还不是很清楚推流,不过可以用这个模型推一个视频文件。
客户端代码:
#include <iostream>
#include <string.h>
#include "zmq.hpp"
#include <unistd.h>
#include <arpa/inet.h>
#include <stdio.h>
using namespace std;
// export LD_LIBRARY_PATH="&LD_LIBRARY_PATH:/usr/local/lib"
// g++ main.cpp -lzmq -o main
/**
* @brief Main构造函数
* @param
* @retval
*/
char file_buff[1024];
int file_one_read = 1024;
int main(int argc, char **argv)
{
if(argc != 3) {
return -1;
}
char *ip = argv[1];
char *file = argv[2];
printf("ip = %s %s\n", ip, file);
zmq::context_t context (1);
char buff[200];
snprintf(buff, sizeof(buff), "tcp://%s", ip);
zmq::socket_t socket(context, ZMQ_PUSH);
socket.setsockopt(ZMQ_SNDTIMEO, 0);
//socket.connect(buff);
socket.connect("tcp://122.51.111.216:5555");
sleep(1);
zmq::message_t mess(128);
long file_path_len = strlen(file);
file_path_len = htonl(file_path_len);
printf("file %d %s\n", file_path_len, file);
socket.send(&file_path_len, 4, ZMQ_SNDMORE);
socket.send(file, strlen(file), ZMQ_SNDMORE);
//socket.send(mess, ZMQ_SNDMORE);
FILE *fp = fopen(file, "rb");
if(fp == NULL) {
printf("file no exist\n");
return -1;
}
int size = 0;
int pos = size;
int count = 0;
while(1)
{
fseek(fp, pos, SEEK_SET);
size = fread(file_buff, 1, sizeof(file_buff), fp);
if(size < 0) {
printf("read file fail\n");
socket.send(file_buff, 0, 0);
break;
} else if(size == file_one_read) {
socket.send(file_buff, size, ZMQ_SNDMORE);
printf("send %d\n", count++);
} else if(size < file_one_read) {
socket.send(file_buff, size, 0);
printf("send file succces %s\n", file_buff);
break;
}
pos += size;
}
fclose(fp);
socket.close();
context.close();
return 0;
}
服务器端代码:
int main(int argc, char **argv)
{
zmq::context_t context (1);
zmq::socket_t socket(context, ZMQ_PULL);
socket.setsockopt(ZMQ_RCVTIMEO, 0);
socket.bind("tcp://*:5555");
zmq::pollitem_t items [] = {
{ socket, 0, ZMQ_POLLIN, 0 }
};
long file_path_len = 0;
char file[128] = {0};
// file_head_t head;
while(1)
{
int rc = zmq::poll (&items[0], 1, -1);
if (items[0].revents & ZMQ_POLLIN)
{
zmq::message_t mess;
socket.recv(&mess);
memcpy(&file_path_len, mess.data(), sizeof(file_path_len));
printf("file %d\n", file_path_len);
file_path_len = ntohl(file_path_len);
socket.recv(&mess);
memcpy(file, mess.data(), file_path_len);
printf("接收到的数据 %d %d %s\n", mess.size(), file_path_len, file);
FILE *fp = fopen(file, "wb+");
if(fp == NULL) {
printf("file no exist\n");
//goto exit;
}
while(1) {
//socket.recv(&mess);
bool more = mess.more();
printf("more %d\n", more);
if(more) { //还有内容
socket.recv(&mess);
printf("ss %s\n", mess.data());
fwrite(mess.data(), 1, mess.size(), fp);
} else { //没有其他内容了
break;
}
}
fclose(fp);
printf("recv file sussce\n");
}
items[0].revents = 0;
}
exit:
socket.close();
context.close();
return 0;
}
这个模型需要注意的是,我们使用的是发送多针包,需要发送一个结束帧之后,服务器才能接受到数据,当初就是不知道这个,一直在测试,怎么就接受不到数据。
24.2 独立对模型
独立对模式用来精确的连接另一个对端。这种模式通过inproc方式用来进程内部通信。
24.2.1 ZMQ_PAIR
ZMQ_PAIR类型的套接字在每一时刻只能链接到一个对端上。不会有消息被路由或者过滤的操作发生在发送给ZMQ_PAIR套接字的的消息上。
当ZMQ_PAIR类型的套接字由于连接的对端到达高水位而进入静默状态,或者没有可用的对端可以连接,在这个套戒指上的所有zmq_send(3)操作都会进入阻塞,直到本套接字可以发送消息为止;消息不会被丢弃。
这个进程通信,目前不细讲,以后用到的时候再讲讲。
24.3 本地模型
本机模式用于与TCP对等端通信,并允许异步请求和双向应答。
24.3.1 ZMQ_STREAM
使用tcp://传输时,类型为ZMQ_STREAM的套接字用于从非ØMQ对等方发送和接收TCP数据。 ZMQ_STREAM套接字可以充当客户端和/或服务器,异步发送和/或接收TCP数据。
当接收TCP数据时,在将消息传递给应用程序之前,ZMQ_STREAM套接字应在消息部分之前包含消息的始发对等方的路由ID。 接收到的消息在所有连接的同级之间公平排队。
发送TCP数据时,ZMQ_STREAM套接字应删除消息的第一部分,并使用它来确定消息应路由到的对等方的路由ID,并且不可路由的消息将导致EHOSTUNREACH或EAGAIN错误。
要打开与服务器的连接,请使用zmq_connect调用,然后使用带有ZMQ_ROUTING_ID选项的zmq_getsockopt调用获取套接字路由ID。
要关闭特定连接,请发送路由ID帧,后跟零长度消息(请参见示例部分)。
建立连接后,应用程序将收到零长度的消息。 同样,当对等方断开连接(或连接断开)时,应用程序将收到零长度消息。
您必须先发送一个路由ID帧,然后发送一个数据帧。 ZMQ_SNDMORE标志是路由ID帧所必需的,但在数据帧上将被忽略。
这个我是直接翻译复制过来,不知道这个zmq为什么会写这个的例子,可能是本机测试的把。
void *ctx = zmq_ctx_new ();
assert (ctx);
/* Create ZMQ_STREAM socket */
void *socket = zmq_socket (ctx, ZMQ_STREAM);
assert (socket);
int rc = zmq_bind (socket, "tcp://*:8080");
assert (rc == 0);
/* Data structure to hold the ZMQ_STREAM routing id */
uint8_t routing_id [256];
size_t routing_id_size = 256;
/* Data structure to hold the ZMQ_STREAM received data */
uint8_t raw [256];
size_t raw_size = 256;
while (1) {
/* Get HTTP request; routing id frame and then request */
routing_id_size = zmq_recv (socket, routing_id, 256, 0);
assert (routing_id_size > 0);
do {
raw_size = zmq_recv (socket, raw, 256, 0);
assert (raw_size >= 0);
} while (raw_size == 256);
/* Prepares the response */
char http_response [] =
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"Hello, World!";
/* Sends the routing id frame followed by the response */
zmq_send (socket, routing_id, routing_id_size, ZMQ_SNDMORE);
zmq_send (socket, http_response, strlen (http_response), 0);
/* Closes the connection by sending the routing id frame followed by a zero response */
zmq_send (socket, routing_id, routing_id_size, ZMQ_SNDMORE);
zmq_send (socket, 0, 0, 0);
}
zmq_close (socket); zmq_ctx_destroy (ctx);
24.4 Request-reply 模型
请求答复模型,这个模型比较重要,要好好讲讲。
24.4.1 ZMQ_REQ/ZMQ_REP
这个模式就是请求-回复模式,客户端发送一个请求,服务器就回复,这个在上节,已经讲过了,这里就不讲了。
24.4.2 ZMQ_ROUTER/ZMQ_DEALER
这一对套接字也是为了解决上面的REP/REQ的水平不扩展性,而想出来的办法,这个跟我讲的XPUB/XSUB差不多,也是需要代理也解决的,这里的代码就不写了,因为看着差不多。
zmq_socket(3),这个是socket的官方API。
补充内容:
24.5 zmq设置消息路由
有可能在实际的应用中,有几个消息是需要顺序执行的,是有依赖关系的,所以这个时候就需要把这几个有关系的消息,统一发送到一个结点上,如果随机发送到不同结点的话,可能执行的顺序就会乱,所以这个需要我们需要设置路由信息。但是这里比较懒,好像也没使用到这种地步,所以目前先不写,以后碰到了再回来看看。
24.6 zmq测试
官方给出了测试实现的代码:libzmq-master\perf
就在下载的源码包里,以后如果需要做测试的话,可以参考一下这个程序,这里留个笔记。
ZMQ的测试方法:
http://wiki.zeromq.org/whitepapers:measuring-performance
ZMQ的测试结果:
http://wiki.zeromq.org/area:results