Summer of Sockets Part 2: Request and Response With ZeroMQ

Z

eroMQ makes it incredibly easy to connect and communicate between individual applications in distinctive patterns. Last time we took a look at the simple PUSH / PULL socket types. The next types we'll take a look at are the Request / Reply ( REQ / REP ) sockets. You can download the source code for the following examples if you would like to follow along.

Request & Reply sockets are probably the most familiar and easiest to reason about for web developers. This most obvious use case with REQ and REQ sockets, is a web server. A client ( REQ ) makes a request to the server ( REP ) and it waits until a response comes back. Unlike the PUSH socket, the REQ socket will wait until it receives a reply before it sends another message.

Server

We can set up a simple ZMQ server using the REP socket.

var zmq = require('zmq')  
   ,server = zmq.socket('rep')
   ,count = 0;

// listen for the message event.
server.on('message', function(d){  
    // make every 3rd message wait for a bit...
    setTimeout(function(){
        server.send(JSON.stringify({response:d.toString()}))
    }, ++count % 3 === 0 ? 1000 : 1 )
});

// bind to port 5454
server.bind('tcp://0.0.0.0:5454', function(err){  
    if(err){
        console.error("something bad happened");
        console.error( err.msg );
        console.error( err.stack );
        process.exit(0);
    }
})

Client

The server is pretty plain. It just listens for requests on port 5454 and response as fast as it can. However, every third request, it will wait for a full second to return a response to the client. The client code for our little experiment is even more simple, we just need to spam the server with requests.

var zmq = require('zmq')  
   ,client = zmq.socket('req')
   ,msgcount = 0;

// listen for responses from the server
client.on('message', function(d){  
    console.log(JSON.parse( d ))
});

// connect to the server port
client.connect('tcp://0.0.0.0:5454');  
setInterval( function(){  
    client.send(msgcount++)
    // spit out the number of queued requests
    console.log( "%d message queued", client._outgoing.length )
},100)

To run this, you should start the server first, then start the client. As the client runs, you'll see that every 3rd request, the server waits for a second, and during that time the client is still trying to send requests and queuing them until the server response. You should see some output like this:

0 message queued  
1 message queued  
2 message queued  
3 message queued  
4 message queued  
5 message queued  
6 message queued  
7 message queued  
8 message queued  
9 message queued  
{ response: 1 }
{ response: 2 }
{ response: 3 }
7 message queued  
8 message queued  
9 message queued  
10 message queued  
11 message queued  
12 message queued  
13 message queued  
14 message queued  
15 message queued  
16 message queued  
{ response: 4 }
{ response: 5 }
{ response: 6 }
14 message queued  
15 message queued  
16 message queued  
17 message queued  

This is an important distinction from the PUSH / PULL - messages are queued on the client and the server dictates when it is able to receive more work. ZMQ uses a special identity frame which it appends to all messages from the REQ socket so the server knows where to send the response. Only when the REQ socket receives a response for the last message that it sent, will it send another request.

A Request Client will queue messages until it's previous message has been acknowledged by a Reply server. if a server never replies, then messages will be queued indefinitely

It will become more obvious if you were to stop and restart the REP server process. While the server is stopped, the client just keeps running and queuing up messages. When the server restarts, you'll notice that the client does not start receiving responses from the server. This is because the server exited before it was able to return the with the corresponding identity frame that the client was expecting. It is really up to the application logic to decide how to handle situations when a server may go down, and what to do with the messages.

So we have seen two pretty simple patterns,PUSH / PULL and REQ / REP. PUSH / PULL exhibits a Fair Queuing or a round robin distribution of work to connected clients. REQ / REP exhibits a more old school style, synchronous request & response style of messaging. While situations where a vanilla REQ / REP infrastructure is useful are few and far between, it is an important concept the understand before we move on.

javascript node.js zmq socket request reply summer of sockets