Architecture

Highest level overview

By importing the bindings into your project, you will be able to serialize and deserialize messages over the wire. You can have multiple clients interacting with the engine at the same time.

The key networking pattern that allows us to do this is called the Dealer-Router pattern. We highly recommend you read more about it in the ZeroMQ socket api as well as the ZeroMQ guide.

Basic Routing Mechanism

Note that a Dealer socket can be given an identity. When you Push a request to the engine with the identity of the Dealer socket, the engine will route the response to that Dealer socket.

I.e, John (Push socket) tells Mike (engine) to do some work and then give the finished product to Jim (Dealer socket). If there is another Jim at the office, he will have to change his name. Those are the rules of this pattern.

In our C++ sample, you will see that we declare a Dealer socket and then give it a randomly generated ID addr_dst. What the identity is doesn’t matter, but it must be unique.

//c++
sock_synchro = socket_t(zcontext, ZMQ_DEALER);
sock_synchro.setsockopt(ZMQ_IDENTITY, addr_dest.c_str(), addr_dest.size());
sock_synchro.connect("tcp://127.0.0.1:11556");

The important thing to notice is that each Dealer how has a unique identity. Once the Parietal Numerics engine receives a job request, it will know where to route the result.

In the C++ constructor, notice how we generate a GUID and assign it to the dealer identities.

boost::uuids::random_generator randgen;
context_t zcontext;
string client_id;
string addr_orgn;
string addr_dest;
string addr_dest_survey;

PC()
{
    cout << "initializing context";

    client_id = boost::uuids::to_string(randgen());
    addr_orgn = client_id + "-origin";
    addr_dest = client_id + "-target";
    addr_dest_survey = client_id + "-SurveyTarget";

    zcontext = context_t(1);

    sock_push = socket_t(zcontext, socket_type::push);
    sock_push.connect("tcp://localhost:11555");

    sock_synchro = socket_t(zcontext, ZMQ_DEALER);
    sock_synchro.setsockopt(ZMQ_IDENTITY, addr_dest.c_str(), addr_dest.size());
    sock_synchro.connect("tcp://127.0.0.1:11556");

    sock_async = socket_t(zcontext, socket_type::dealer);
    sock_async.setsockopt(ZMQ_IDENTITY, addr_dest.c_str(), addr_dest.size());
    sock_async.connect("tcp://127.0.0.1:11557");

    sock_survey = socket_t(zcontext, socket_type::dealer);
    sock_survey.setsockopt(ZMQ_IDENTITY, addr_dest_survey.c_str(), addr_dest_survey.size());
    sock_survey.connect("tcp://127.0.0.1:11558");

    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

Therefore on a per client basis you get the following routing graph:

We let the client thread sleep for a little bit so that the sockets can set themselves up. How long you want to wait, is up to you and maybe 100ms is a bit excessive.

Only 1 Context

There should be only one ZeroMQ context in your program. How you implement this as a pattern in your project is up to you. You could go with a Singleton or a Static Class. Another reason is that there is not much of a reason to keep declaring and destroying sockets (unless they get confused… they sometimes do). Again, we recommend reading the ZeroMQ guide on how to ‘poll’ sockets. See the Advanced Request-Reply Pattern section. If you do, you might need to excise the dealer sockets out of the Singleton and implement a creation/destruction pattern.