1. ZeroMQ背景知识(version-4.2.0)
ZMQ(以下ZeroMQ简称ZMQ)是一个简单好用的、轻量级的消息组件,像框架一样的一个socket library。它的本质是个消息处理队列库,在BSD socket基础上做了一层封装,将网络通讯、进程通讯和线程通讯抽象为统一的接口,可在多个线程、内核和主机盒之间弹性伸缩。
2. ZMQ的几种模型
- Client-server模型(http://rfc.zeromq.org/spec:41/CLISRV/)
- client端可以连接一个或多个server。当连接到多个server时,发送数据采用轮询的方式;接收数据采用公平队列按序接收。
- client端在没有可用连接时会阻塞,或者返回错误。(除非特殊配置)
- server端可以连接0或多个client。它只能回复client的请求信息,应答数据会送到指定的client端。接收数据采用公平队列按序接受。
- server端应答时,若client端接收队列已满,会马上返回(重试),不会阻塞。
- 任何一对client-server都是全双工的,每端都会维护两个队列用于收发数据。
- 无论哪端,都不会因为不能入队列丢弃消息。
- client和server套接字是线程安全的。
- Publish-subscribe模型(http://rfc.zeromq.org/spec:29)
- pub端向一系列sub或xsub端广播数据,并且只发数据,会丢弃所有从sub端(一般是xsub)发送过来的数据。
- pub端在sub端接收队列已满的情况下,会丢弃发送数据,不会堵塞。
- pub端会将数据发送到所有订阅消息的sub端。
- xpub拓展了pub,可以从同时收发数据。在接收数据时,会将订阅和非订阅的sub端数据提交给应用程序。
- sub端连接了任意多个pub或xpub端,只会接收数据(除向pub端发送订阅非订阅外),采用公平队列策略。
- xsub拓展了sub,可以同时收发数据。在pub端接收队列已满的情况下,会丢弃所发送的数据,不会阻塞。
- Pipline模型(http://rfc.zeromq.org/spec:30)
- push端采用轮询的方式向任意多个pull端发送数据。
- push端在没有可用连接时,发送数据会阻塞,或者返回一个错误。
- push端在无可用连接时不会接收应用程序的数据,因此不会丢弃数据。
- pull端采用公平队列的方式接收一系列push端的数据。
- push端不能接收数据,同样,pull端不能发送数据。
- Exclusive pair模型(http://rfc.zeromq.org/spec:31)
- peer两端互连,一个用于进程间通讯的模型
- 当peer进入静音模式时(缓冲区满或未建立连接),任何一方的发送数据都会被阻塞,因此不会丢弃数据。
- Native模型
- 用于TCP连接两端进行异步请求和应答的模型。
- 在将接收到的数据传递给应用程序之前,socket会提前准备一个包含数据发送端id的消息。
- 在发送消息时,会移除消息的第一部分用于路由选择,再将剩余的消息发送到指定id的接收端。不能路由的消息会返回错误信息并发送失败。
// Create a new ZMQ context
void *context = zmq_ctx_new();
// For client-server pattern
void *client_receiver = zmq_socket(context, ZMQ_CLIENT)
void *server_receiver = zmq_socket(context, ZMQ_SERVER)
// For publish-subscribe pattern
void *pub_receiver = zmq_socket(context, ZMQ_PUB)
void *xpub_receiver = zmq_socket(context, ZMQ_XPUB)
void *sub_receiver = zmq_socket(context, ZMQ_SUB)
void *xsub_receiver = zmq_socket(context, ZMQ_XSUB)
// For pipeline pattern
void *push_receiver = zmq_socket(context, ZMQ_PUSH)
void *pull_receiver = zmq_socket(context, ZMQ_PULL)
// For exclusive pair pattern
void *pair_receiver = zmq_socket(context, ZMQ_PAIR)
int rc;
rc = zmq_bind(pair_receiver, "inproc://#1")
assert(rc == 0);
rc = zmq_connect(pair_receiver, "inproc://#1")
assert(rc == 0);
// Native pattern
void *stream_receiver = zmq_socket(context, ZMQ_STREAM)
3. I/O模型
- (同步)阻塞式I/O模型
-
默认情形下,所有的socket都是阻塞的。以用户线程在内核进行IO操作为例,用户在发起read系统调用后发生阻塞,转到内核空间,内核等到数据到达后将数据拷到用户空间,完成操作,线程返回。如下图所示:
-
- (同步)非阻塞式I/O模型
-
通过将socket设置为非阻塞式的,可以在IO不能立即响应时返回一个错误,此时并没有数据返回。用户线程需不断重复发起IO请求,直到数据到达后才能得到真正的数据,剩余部分和阻塞式I/O相同。如下图所示:
- 非阻塞式I/O模型一般很少直接使用,往往实在其他模型中使用非阻塞这一特性。因为该模型需要反复发起IO请求,消耗了大量的CPU资源。
-
- (同步)I/O复用模型
-
建立在内核提供的select基础之上,可以避免非阻塞式I/O模型中轮询等待的问题。与阻塞式I/O不同,I/O复用模型会阻塞在select或poll调用处,直到等待的套接字中有变为可读的。如下图所示:
-
- 异步I/O模型
-
异步I/O需要操作系统更强的支持,与I/O复用不同,异步I/O不需要用户线程自行读取数据、处理数据,而是讲这些工作讲给操作系统完成,只接收内核通知I/O事件已经完成。如下图所示:
- 异步I/O中,用户需要自行编写处理数据的回调程序并递交给操作系统。相比较于I/O复用,异步I/O并十分常用。很多高性能并发服务器使用I/O复用加多线程任务处理的架构基本可以满足需求。
-