2
votes

ZeroMQ: Messaging for Many Applications comments on REQ -> ROUTER communication:

If we rewrote our “Hello World” server using ROUTER, we’d be able to process any number of “Hello” requests in parallel.

So I typed out hwclient.hs and have slightly modified the hwserver.hs from the ZeroMQ Guide:

Hello World Client

{-# LANGUAGE OverloadedStrings #-}

-- Hello World client

module Main where

import Control.Monad
import System.ZMQ4.Monadic

main :: IO ()
main = runZMQ $ do
    liftIO $ putStrLn "Connecting to hello world server…"

    requester <- socket Req
    connect requester "tcp://localhost:5555"

    forM_ [1..10] $ \i -> do
        liftIO . putStrLn $ "Sending Hello " ++ show i ++ "…"
        send requester [] "Hello"
        _ <- receive requester
        liftIO . putStrLn $ "Received World " ++ show i

And the Router:

Hello World Router

{-# LANGUAGE OverloadedStrings #-}

-- Hello World server

module Main where

import Control.Concurrent
import Control.Monad
import System.ZMQ4.Monadic

main :: IO ()
main = runZMQ $ do
    -- Socket to talk to clients
    responder <- socket Router
    bind responder "tcp://*:5555"

    forever $ do
        buffer <- receive responder
        liftIO $ do
            putStrLn "Received Hello"
            threadDelay 1000000 -- Do some 'work'
        send responder [] "World"

But, when I run it, I see the client send a message, but never receive a response.

$stack exec -- client-exe
Connecting to hello world server…
Sending Hello 1…

In my other window, I see the server receives 3 messages and nothing more:

$stack exec -- server-exe
Received Hello
Received Hello
Received Hello

Q1: Why does the client never receive a reply?
Q2: And why does the server (ROUTER) receive 3 messages rather than 1?

EDIT

I updated hwserver.hs to print out the messages that it receives rather than simply "Hello".

Posting the diff:

printf "received request: %s\n" . unpack $ buffer

replaced

putStrLn "Received Hello"

And the server (Router) showed:

$stack exec -- server-exe
received request: A§
received request: 
received request: Hello

Lastly, here's the send's function signature:

λ: :t send
send
  :: Sender t =>
     Socket z t
     -> [Flag] -> Data.ByteString.Internal.ByteString -> ZMQ z ()
2
Does the Haskell binding detect & handle a mandatory part of the API REQ/ROUTER behaviour automatically? (cit.: "When a ZMQ_REQ socket is connected to a ZMQ_ROUTER socket, in addition to the identity of the originating peer each message received shall contain an empty delimiter message part. Hence, the entire structure of each received message as seen by the application becomes: one or more identity parts, delimiter part, one or more body parts. When sending replies to a ZMQ_REQ socket the application must include the delimiter part." )user3666197
You would also like the full story by Pieter Hintjens' book [ Code Connected, Vol. 1 ], page 97 ( >>> hintjens.wdfiles.com/local--files/main%3Afiles/cc1pe.pdf ), (cit.: "The worker has to save the envelope ( which is all the parts up to and including the empty message frame ) and then it can do what’ s needed with the data part. Note that a REP socket would do this automatically, but we’ re using the REQ-ROUTER pattern ... " )user3666197
( cit.: We can use ROUTER in two distinct ways: • As a proxy that switches messages between frontend and backend sockets. • As an application that reads the message and acts on it. In the first case, the ROUTER simply reads all frames, including the artificial identity frame, and passes them on blindly. In the second case the ROUTER must know the format of the reply envelope it’ s being sent. As the other peer is a REQ socket, the ROUTER gets the identity frame, an empty frame, and then the data frame."user3666197

2 Answers

4
votes

Answering in reverse order...

Your 3 'messages' are the 3 frames of the recieved message. The first frame is added by the Router socket to identify where the message came from. The 2nd is an empty delimiter frame added by the Requester socket and the final frame contains the data you sent.

This leads on to why you don't get a response, when a dealer socket sends a message it strips off the first message frame and uses it as an address to identify the client it needs to reply to. As you aren't saving and appending the first frame received to the outgoing message then it'll just be dropped.

Sadly I don't know enough about the haskell or its zeromq bindings to give you a code solution, but if you receive into an address buffer until you read an empty packet, then read the data packet into the data buffer. When replying send the address buffer, an empty buffer and then the reply.

1
votes

With David's and user3666197's responses that I was discarding the identity frame, I came up with:

{-# LANGUAGE OverloadedStrings #-}

-- Hello World server

module Main where

import Control.Concurrent
import Control.Monad
import System.ZMQ4.Monadic
import Text.Printf
import Data.ByteString.Char8 (unpack, pack)
import Data.List.NonEmpty

main :: IO ()
main = runZMQ $ do
    -- Socket to talk to clients
    responder <- socket Router
    bind responder "tcp://*:5555"

    forever $ do
        response @ (identity : _ : body : []) <- receiveMulti responder
        _ <- liftIO $ putStrLn . show . Prelude.length $ response
        _ <- liftIO $ forM_ response (printf "received request: %s\n" . unpack)
        liftIO $ putStrLn "sending world"
        sendMulti responder $ identity `cons` (pack "" `cons` (pack "world" :| []))

Running it

-- terminal 1
$stack exec -- client-exe
Connecting to hello world server…
Sending Hello 1…
received request: world
...

--terminal 2
$stack exec -- router-exe
3
received request: A§
received request: 
received request: Hello
sending world  
...