在请求-应答模式中,信封里保存了应答目标的位置。这就是为什么ØMQ网络虽然是无状态的,但仍能完成请求-应答的过程。
在一般使用过程中,你并不需要知道请求-应答信封的工作原理。使用REQ、REP时,ØMQ会自动处理消息信封。下一章讲到的装置(device),使用时也只需保证读取和写入所有的信息即可。ØMQ使用多段消息的方式来存储信封,所以在复制消息时也会复制信封。
然而,在使用高级请求-应答模式之前是需要了解信封这一机制的,以下是信封机制在ROUTER中的工作原理:
- 从ROUTER中读取一条消息时,ØMQ会包上一层信封,上面注明了消息的来源。
- 向ROUTER写入一条消息时(包含信封),ØMQ会将信封拆开,并将消息递送给相应的对象。
如果将从ROUTER A中获取的消息(包含信封)写入ROUTER B(即将消息发送给一个DEALER,该DEALER连接到了ROUTER),那么在从ROUTER B中获取该消息时就会包含两层信封。
信封机制的根本作用是让ROUTER知道如何将消息递送给正确的应答目标,你需要做的就是在程序中保留好该信封。回顾一下REP套接字,它会将收到消息的信封逐个拆开,将消息本身传送给应用程序。而在发送时,又会在消息外层包裹该信封,发送给ROUTER,从而传递给正确的应答目标。
我们可以使用上述原理建立起一个ROUTER-DEALER装置:
[REQ] <--> [REP]
[REQ] <--> [ROUTER--DEALER] <--> [REP]
[REQ] <--> [ROUTER--DEALER] <--> [ROUTER--DEALER] <--> [REP]
...etc.
当你用REQ套接字去连接ROUTER套接字,并发送一条请求消息,你会从ROUTER中获得一条如下所示的消息:
- 第三帧是应用程序发送给REQ套接字的消息;
- 第二帧的空信息是REQ套接字在发送消息给ROUTER之前添加的;
- 第一帧即信封,是由ROUTER套接字添加的,记录了消息的来源。
如果我们在一条装置链路上传递该消息,最终会得到包含多层信封的消息。最新的信封会在消息的顶部。
以下将详述我们在请求-应答模式中使用到的四种套接字类型:
-
DEALER是一种负载均衡,它会将消息分发给已连接的节点,并使用公平队列的机制处理接受到的消息。DEALER的作用就像是PUSH和PULL的结合。
-
REQ发送消息时会在消息顶部插入一个空帧,接受时会将空帧移去。其实REQ是建立在DEALER之上的,但REQ只有当消息发送并接受到回应后才能继续运行。
-
ROUTER在收到消息时会在顶部添加一个信封,标记消息来源。发送时会通过该信封决定哪个节点可以获取到该条消息。
-
REP在收到消息时会将第一个空帧之前的所有信息保存起来,将原始信息传送给应用程序。在发送消息时,REP会用刚才保存的信息包裹应答消息。REP其实是建立在ROUTER之上的,但和REQ一样,必须完成接受和发送这两个动作后才能继续。
REP要求消息中的信封由一个空帧结束,所以如果你没有用REQ发送消息,则需要自己在消息中添加这个空帧。
你肯定会问,ROUTER是怎么标识消息的来源的?答案当然是套接字的标识。我们之前讲过,一个套接字可能是瞬时的,它所连接的套接字(如ROUTER)则会给它生成一个标识,与之相关联。一个套接字也可以显式地给自己定义一个标识,这样其他套接字就可以直接使用了。
这是一个瞬时的套接字,ROUTER会自动生成一个UUID来标识消息的来源。
这是一个持久的套接字,标识由消息来源自己指定。
下面让我们在实例中观察上述两种操作。下列程序会打印出ROUTER从两个REP套接字中获得的消息,其中一个没有指定标识,另一个指定了“Hello”作为标识。
#include "../zhelpers.h" int main (void) { void *context = zmq_ctx_new (); void *sink = zmq_socket (context, ZMQ_ROUTER); zmq_bind (sink, "inproc://example"); // First allow 0MQ to set the identity void *anonymous = zmq_socket (context, ZMQ_REQ); zmq_connect (anonymous, "inproc://example"); s_send (anonymous, "ROUTER uses a generated 5 byte identity"); s_dump (sink); // Then set the identity ourselves void *identified = zmq_socket (context, ZMQ_REQ); zmq_setsockopt (identified, ZMQ_IDENTITY, "PEER2", 5); zmq_connect (identified, "inproc://example"); s_send (identified, "ROUTER socket uses REQ's socket identity"); s_dump (sink); zmq_close (sink); zmq_close (anonymous); zmq_close (identified); zmq_ctx_destroy (context); return 0; }
out:
---------------------------------------- [005] 006B8B4567 [000] [039] ROUTER uses a generated 5 byte identity ---------------------------------------- [005] PEER2 [000] [040] ROUTER socket uses REQ's socket identity