jeroMq示例之[2] [req-rep-envelopes msg identity]
Request-Reply Envelopes
在请求-应答模式中,信封里保存了应答目标的位置。这就是为什么Ø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”作为标识。
1 package guide; 2 3 import org.zeromq.ZMQ; 4 import org.zeromq.ZMQ.Context; 5 import org.zeromq.ZMQ.Socket; 6 7 /** 8 * Demonstrate identities as used by the request-reply pattern. 9 */ 10 public class identity { 11 12 public static void main (String[] args) throws InterruptedException { 13 14 Context context = ZMQ.context(1); 15 Socket sink = context.socket(ZMQ.ROUTER); 16 sink.bind("inproc://example"); 17 18 // First allow 0MQ to set the identity, [00] + random 4byte 19 Socket anonymous = context.socket(ZMQ.REQ); 20 21 anonymous.connect("inproc://example"); 22 anonymous.send ("ROUTER uses a generated UUID",0); 23 ZHelper.dump (sink); 24 25 // Then set the identity ourself 26 Socket identified = context.socket(ZMQ.REQ); 27 identified.setIdentity("Hello".getBytes (ZMQ.CHARSET)); 28 identified.connect ("inproc://example"); 29 identified.send("ROUTER socket uses REQ's socket identity", 0); 30 ZHelper.dump (sink); 31 32 sink.close (); 33 anonymous.close (); 34 identified.close(); 35 context.term(); 36 37 } 38 }
dump 出来三帧消息,第一帧为id
----------------------------------------
[005] 00F275910E
[000]
[028] ROUTER uses a generated UUID
----------------------------------------
[005] Hello
[000]
[040] ROUTER socket uses REQ's socket identity