zeromq的尝鲜笔记之一。内容包含ROUTER socket的理解介绍,一个小代码片段,以及czmq中处理消息帧的api的用法。
试用说白了就是用zeromq写套小东西。期间必然会遇到问题,笔记的目的无非就是记录问题,加深理解。而实际上我在记录的过程中也不断地修正了一些起初想当然的错误的理解。虽说已极力避免,但由于都是自己一人的理解,错误在所难免,希望有兴趣看的同学帮我指出。
目标是依赖Zeromq实现一个支持并发的server端,接收多个client的请求,略作处理后返回响应。
client端是windows上的java程序。server端则是CentOS下的cpp程序。
若直接用REQ-REP模式的话,需要注意到:REP端必须严格遵循recv,send,recv,send….的步骤,倘若REP端在recv后需要一定时间的处理之后才能send,那么接下来的下一个REQ就得被迫等着了。所以从外部看,我们的REP的并发只有1。就跟下面这张图一样,步骤4的消息必须等到步骤3之后才能被接收。
这可能是有些场景下必须的,但不是我想要的。因为我们需要并发。说白了就是我们要在步骤2的执行期间,把步骤4甚至接下来的5、6都做了。
REP之所以要按部就班,因为它如果不按部就班,就不知道把响应发回给哪里,所以它必须要同步地,先recv再send。
我们再来看ROUTER。它之所以可以不按部就班,是因为它收到REQ的消息时,在消息头上加入来源地址,然后再交给客户端。发送时,取出消息第一帧作为目标地址,将空帧之后的帧进行发送。
举例来说,app1通过REQ发送给通过ROUTER接收的app2。若app1发送的是
<code>1</code>
<code>["hello"]</code>
,经由ROUTER的处理,app2应用层得到的消息将是
<code>[app1's address|empty|"hello"]</code>
对于app2,不能只关心业务数据”hello”,还需要将app1′s address缓存下来,用以响应的回复。比如,若app2要回复”world”,需要手动构造一个有三个frame的消息:
<code>[app1's address|empty|"world"]</code>
再把这个消息交给ROUTER socket进行send,这时ROUTER会将第一帧address取出作为目标地址——也就是的REQ端——,再将空帧之后的数据发出。所以最终REQ端收到响应为:
<code>["world"]</code>
这是代码片段。
<code>void</code> <code>*receiver = zsocket_new(ctx, ZMQ_ROUTER);</code>
<code>2</code>
<code>zsocket_bind(receiver, </code><code>"ipc:///tmp/0"</code><code>);</code>
<code>3</code>
<code>4</code>
<code>....</code>
<code>5</code>
<code>6</code>
<code>//recv message</code>
<code>7</code>
<code>zmsg_t *msg = zmsg_recv(receiver);</code>
<code>8</code>
<code>if</code> <code>(!msg){</code>
<code>9</code>
<code> </code><code>//error handle..</code>
<code>10</code>
<code> </code><code>return</code><code>;</code>
<code>11</code>
<code>}</code>
<code>12</code>
<code>zframe_t *address = zmsg_unwrap (msg);</code>
<code>13</code>
<code>zframe_t *frame = zmsg_first (msg);</code>
<code>14</code>
<code>zmsg_destroy (&msg);</code>
<code>15</code>
<code>16</code>
<code>//do something, it make time some times...</code>
<code>17</code>
<code>18</code>
<code>//make response message</code>
<code>19</code>
<code>zmsg_t *message = zmsg_new();</code>
<code>20</code>
<code>zframe_t* body = zframe_new(</code><code>"world"</code><code>,</code><code>sizeof</code><code>(</code><code>"world"</code><code>));</code>
<code>21</code>
<code>22</code>
<code>zmsg_push (message, body);</code>
<code>23</code>
<code>zmsg_wrap (message, address);</code>
<code>24</code>
<code>25</code>
<code>zmsg_send (&message, receiver);</code>
<code>26</code>
<code>zmsg_destroy(&message);</code>
需要说明的是czmq里的几个函数,万万不可用错。虽然有注释,但要用对这些api首先得搞清楚里面提到的first,front在message里是怎么个位置,为此我画了个图:
<code>// Pop frame off front of message, caller now owns frame</code>
<code>// If next frame is empty, pops and destroys that empty frame.</code>
<code>zframe_t *</code>
<code> </code><code>zmsg_unwrap (zmsg_t *self);</code>
<code>// Return first frame in message, or null</code>
<code> </code><code>zmsg_first (zmsg_t *self);</code>
<code>// Push frame to front of message, before first frame</code>
<code>void</code>
<code> </code><code>zmsg_push (zmsg_t *self, zframe_t *frame);</code>
<code>// Pushes an empty frame in front of frame</code>
<code> </code><code>zmsg_wrap (zmsg_t *self, zframe_t *frame);</code>
所以回头看代码片段,我们在构造响应消息时先push了一个填充着"world"的帧。再zmsg_wrap了一个填充着address的帧。
zmsg_wrap做了两个事情:1)在"world"之前放上address的帧; 2)在这个帧front放个空帧。