ZeroMQ支持七种通信模式,这些模式分别是:
这是最简单的模式,客户端向服务端发送请求,服务端收到请求并发送响应。这个模式是同步的,客户端必须等待服务端的响应。
这个模式用于广播消息。发布者将消息广播给所有订阅者,订阅者可以选择接收感兴趣的消息。
这个模式用于任务分发和负载均衡。任务由PUSH端发送到多个PULL端,PULL端可以竞争任务并执行。
这个模式支持异步的双向通信。DEALER端和ROUTER端可以互相发送消息,而不需要等待对方的响应。
这个模式是REQ/REP模式的反向。服务端向客户端发送请求,客户端收到请求并发送响应。
这个模式是DEALER/ROUTER模式的反向。ROUTER端与多个DEALER端通信,可以在多个DEALER端之间进行负载均衡。
这个模式支持高级的消息路由。XPUB端发布消息,XSUB端订阅消息,并可以根据自己的需要进行筛选和路由。
这些模式可以组合使用,以适应不同的应用场景。例如,可以使用REP/REQ模式与PUSH/PULL模式结合使用,以实现异步的负载均衡。
这个模式类似于Req/Rep模式,但客户端可以发送多个请求而不需要等待服务端的响应。这个模式常用于支持多个客户端同时连接到服务器。
这个模式类似于Pub/Sub模式,但是消息不是广播给所有订阅者,而是只发送给一个随机的订阅者。这个模式常用于负载均衡和任务分发。
“请求-响应模型”支持的套接字类型有4种:
这个是最简单的模式
c语言实现服务器
#include
#include
#include
#include
#include
#include
// 向socket发送数据, 数据为string
static int s_send(void *socket, char *string);
// 从socket接收数据, 并将数据以字符串的形式返回
static char *s_recv(void *socket);
int main()
{
// 1.创建上下文
void *context = zmq_ctx_new();
// 2.创建、绑定套接字
void *responder = zmq_socket(context, ZMQ_REP);
zmq_bind(responder, "tcp://*:5555");
int rc;
// 3.循环接收数据、发送数据
while(1)
{
// 4.接收数据
char *request = s_recv(responder);
assert(request != NULL);
printf("Request: %s\n", request);
free(request);
// 休眠1秒再继续回复
sleep(1);
// 5.回送数据
char *reply = "World";
rc = s_send(responder, reply);
assert(rc > 0);
}
// 6.关闭套接字、销毁上下文
zmq_close(responder);
zmq_ctx_destroy(context);
return 0;
}
static int s_send(void *socket, char *string)
{
int rc;
zmq_msg_t msg;
zmq_msg_init_size(&msg, 5);
memcpy(zmq_msg_data(&msg), string, strlen(string));
rc = zmq_msg_send(&msg, socket, 0);
zmq_msg_close(&msg);
return rc;
}
static char *s_recv(void *socket)
{
int rc;
zmq_msg_t msg;
zmq_msg_init(&msg);
rc = zmq_msg_recv(&msg, socket, 0);
if(rc == -1)
return NULL;
char *string = (char*)malloc(rc + 1);
memcpy(string, zmq_msg_data(&msg), rc);
zmq_msg_close(&msg);
string[rc] = 0;
return string;
}
客户端
#include
#include
#include
#include
#include
// 向socket发送数据, 数据为string
static int s_send(void *socket, char *string);
// 从socket接收数据, 并将数据以字符串的形式返回
static char *s_recv(void *socket);
int main()
{
// 1.创建上下文
void *context = zmq_ctx_new();
// 2.创建套接字、连接服务器
void *requester = zmq_socket(context, ZMQ_REQ);
zmq_connect(requester, "tcp://localhost:5555");
int rc;
// 3.循环发送数据、接收数据
while(1)
{
// 4.发送数据
char *request = "Hello";
rc = s_send(requester, request);
assert(rc > 0);
// 5.接收回复数据
char *reply = s_recv(requester);
assert(reply != NULL);
printf("Reply: %s\n", reply);
free(reply);
}
// 6.关闭套接字、销毁上下文
zmq_close(requester);
zmq_ctx_destroy(context);
return 0;
}
static int s_send(void *socket, char *string)
{
int rc;
zmq_msg_t msg;
zmq_msg_init_size(&msg, 5);
memcpy(zmq_msg_data(&msg), string, strlen(string));
rc = zmq_msg_send(&msg, socket, 0);
zmq_msg_close(&msg);
return rc;
}
static char *s_recv(void *socket)
{
int rc;
zmq_msg_t msg;
zmq_msg_init(&msg);
rc = zmq_msg_recv(&msg, socket, 0);
if(rc == -1)
return NULL;
char *string = (char*)malloc(rc + 1);
memcpy(string, zmq_msg_data(&msg), rc);
zmq_msg_close(&msg);
string[rc] = 0;
return string;
}
这个模式就类似于同步io阻塞的socker,只能处理一个连接这肯定是不够用的,这就会想到io多路复用epoll
在Dealer/Router模式中,Dealer端可以使用zmq.POLLIN来监听Router端是否有消息到来,这类似于Epoll中监听文件描述符的读事件。在Pub/Sub模式中,订阅者可以使用zmq.POLLIN来监听发布者是否发送了新的消息,这也类似于Epoll中监听读事件。
python实现
服务器
import zmq
context = zmq.Context()
router_socket = context.socket(zmq.ROUTER)
router_socket.bind("tcp://127.0.0.1:5555")
while True:
identity, message = router_socket.recv_multipart()
print(f"Received message from {identity}: {message}")
router_socket.send_multipart([identity, b"ACK"])
客户端
import zmq
context = zmq.Context()
dealer_socket = context.socket(zmq.DEALER)
dealer_socket.identity = b"client1"
dealer_socket.connect("tcp://127.0.0.1:5555")
for i in range(5):
message = f"Hello, this is message {i}"
dealer_socket.send_string(message)
response = dealer_socket.recv()
print(f"Received response: {response}")
Dealer/Router模式还有一种更加高级的用法,用来支持多端之间通信
这里的多端通信就是中间有个Dealer/Router模式作为代理层,代理请求相应,类似于负载均衡,一个客户端不知道自己连的是哪个服务器
这种模式可以运用到日志处理中,通信不使用TCP使用IPC,节省带宽
客户端
#include
#include
#include
#include
// 向socket发送数据, 数据为string
static int s_send(void *socket, char *string);
// 从socket接收数据, 并将数据以字符串的形式返回
static char *s_recv(void *socket);
int main()
{
int rc;
// 1.初始化上下文
void *context = zmq_ctx_new();
// 2.创建套接字、连接代理的ROUTER端
void *requester = zmq_socket(context, ZMQ_REQ);
rc = zmq_connect(requester, "tcp://localhost:5559");
if(rc == -1)
{
perror("zmq_connect");
zmq_close(requester);
zmq_ctx_destroy(context);
return -1;
}
// 3.循环发送、接收数据(10次)
int request_nbr;
for(request_nbr = 0; request_nbr < 10; request_nbr++)
{
// 4.先发送数据
rc = s_send(requester, "Hello");
if(rc < 0)
{
perror("s_send");
zmq_close(requester);
zmq_ctx_destroy(context);
return -1;
}
// 5.等待响应
char *reply = s_recv(requester);
if(reply == NULL)
{
perror("s_recv");
free(reply);
zmq_close(requester);
zmq_ctx_destroy(context);
return -1;
}
printf("Reply[%d]: %s\n", request_nbr + 1, reply);
free(reply);
}
// 6.关闭套接字、销毁上下文
zmq_close(requester);
zmq_ctx_destroy(context);
return 0;
}
static int s_send(void *socket, char *string)
{
int rc;
zmq_msg_t msg;
zmq_msg_init_size(&msg, 5);
memcpy(zmq_msg_data(&msg), string, strlen(string));
rc = zmq_msg_send(&msg, socket, 0);
zmq_msg_close(&msg);
return rc;
}
static char *s_recv(void *socket)
{
int rc;
zmq_msg_t msg;
zmq_msg_init(&msg);
rc = zmq_msg_recv(&msg, socket, 0);
if(rc == -1)
return NULL;
char *string = (char*)malloc(rc + 1);
memcpy(string, zmq_msg_data(&msg), rc);
zmq_msg_close(&msg);
string[rc] = 0;
return string;
}
#include
#include
#include
#include
#include
// 向socket发送数据, 数据为string
static int s_send(void *socket, char *string);
// 从socket接收数据, 并将数据以字符串的形式返回
static char *s_recv(void *socket);
int main()
{
int rc;
// 1.初始化上下文
void *context = zmq_ctx_new();
// 2.创建套接字、连接代理的DEALER端
void *responder = zmq_socket(context, ZMQ_REP);
rc = zmq_connect(responder, "tcp://localhost:5560");
if(rc == -1)
{
perror("zmq_connect");
zmq_close(responder);
zmq_ctx_destroy(context);
return -1;
}
// 3.循环接收、响应
while(1)
{
// 4.先等待接收数据
char *request = s_recv(responder);
if(request == NULL)
{
perror("s_recv");
free(request);
zmq_close(responder);
zmq_ctx_destroy(context);
return -1;
}
printf("Request: %s\n", request);
free(request);
// 休眠1秒再进行响应
sleep(1);
// 5.响应
rc = s_send(responder, "World");
if(rc < 0)
{
perror("s_send");
zmq_close(responder);
zmq_ctx_destroy(context);
return -1;
}
}
// 6.关闭套接字、销毁上下文
zmq_close(responder);
zmq_ctx_destroy(context);
return 0;
}
static int s_send(void *socket, char *string)
{
int rc;
zmq_msg_t msg;
zmq_msg_init_size(&msg, 5);
memcpy(zmq_msg_data(&msg), string, strlen(string));
rc = zmq_msg_send(&msg, socket, 0);
zmq_msg_close(&msg);
return rc;
}
static char *s_recv(void *socket)
{
int rc;
zmq_msg_t msg;
zmq_msg_init(&msg);
rc = zmq_msg_recv(&msg, socket, 0);
if(rc == -1)
return NULL;
char *string = (char*)malloc(rc + 1);
memcpy(string, zmq_msg_data(&msg), rc);
zmq_msg_close(&msg);
string[rc] = 0;
return string;
}
// rrbroker.c
#include
#include
#include
#include
#include
int main()
{
int rc;
// 1.初始化上下文
void *context = zmq_ctx_new();
// 2.创建、绑定套接字
void *frontend = zmq_socket(context, ZMQ_ROUTER);
void *backend = zmq_socket(context, ZMQ_DEALER);
// ZMQ_ROUTER绑定到5559, 接收客户端的请求
rc = zmq_bind(frontend, "tcp://*:5559");
if(rc == -1)
{
perror("zmq_bind");
zmq_close(frontend);
zmq_close(backend);
zmq_ctx_destroy(context);
return -1;
}
// ZMQ_DEALER绑定到5560, 接收服务端的回复
rc = zmq_bind(backend, "tcp://*:5560");
if(rc == -1)
{
perror("zmq_bind");
zmq_close(frontend);
zmq_close(backend);
zmq_ctx_destroy(context);
return -1;
}
// 3.初始化轮询集合
zmq_pollitem_t items[] = {
{ frontend, 0, ZMQ_POLLIN, 0 },
{ backend, 0, ZMQ_POLLIN, 0 }
};
// 4.在套接字上切换消息
while(1)
{
zmq_msg_t msg;
//多部分消息检测
int more;
// 5.调用zmq_poll轮询消息
rc = zmq_poll(items, 2, -1);
//zmq_poll出错
if(rc == -1)
{
perror("zmq_poll");
zmq_close(frontend);
zmq_close(backend);
zmq_ctx_destroy(context);
return -1;
}
//zmq_poll超时
else if(rc == 0)
continue;
else
{
// 6.如果ROUTER套接字有数据来
if(items[0].revents & ZMQ_POLLIN)
{
while(1)
{
// 从ROUTER上接收数据, 这么数据是客户端发送过来的"Hello"
zmq_msg_init(&msg);
zmq_msg_recv(&msg, frontend, 0);
// 查看是否是接收多部分消息, 如果后面还有数据要接收, 那么more会被置为1
size_t more_size = sizeof(more);
zmq_getsockopt(frontend, ZMQ_RCVMORE, &more, &more_size);
// 接收"Hello"之后, 将数据发送到DEALER上, DEALER会将"Hello"发送给服务端
zmq_msg_send(&msg, backend, more ? ZMQ_SNDMORE : 0);
zmq_msg_close(&msg);
// 如果没有多部分数据可以接收了, 那么退出循环
if(!more)
break;
}
}
// 7.如果DEALER套接字有数据来
if(items[1].revents & ZMQ_POLLIN)
{
while(1)
{
// 接收服务端的响应"World"
zmq_msg_init(&msg);
zmq_msg_recv(&msg, backend, 0);
// 查看是否是接收多部分消息, 如果后面还有数据要接收, 那么more会被置为1
size_t more_size = sizeof(more);
zmq_getsockopt(backend, ZMQ_RCVMORE, &more, &more_size);
// 接收"World"之后, 将数据发送到ROUTER上, ROUTER会将"World"发送给客户端
zmq_msg_send(&msg, frontend, more ? ZMQ_SNDMORE : 0);
zmq_msg_close(&msg);
// 如果没有多部分数据可以接收了, 那么退出循环
if(!more)
break;
}
}
}
}
// 8.关闭套接字、销毁上下文
zmq_close(frontend);
zmq_close(backend);
zmq_ctx_destroy(context);
return 0;
}