Search code examples
c++zeromq

Cppzmq - Router to Router communication gets stuck on initial connect


I'm trying to recreate the Router-to-Router example from PyZMQ using cppzmq. I tested the Python version and the behavior is as expected (i.e., client connects to server, server sends reply).

However, my C++ version gets stuck on the initial connect (or throws a host unreachable error if router_mandatory is set). Changing the client socket type to dealer leads to the expected message flow.

I suspect that I messed up the identities/routing somehow, but I'm too stuck to see where I went wrong.

server.cpp

#include <zmq.hpp>
#include <zmq_addon.hpp>
#include <iostream>
#include <vector>
#include <string>

int main() {
    zmq::context_t context(1);
    zmq::socket_t server(context, zmq::socket_type::router);
    server.set(zmq::sockopt::routing_id, "server");
    server.bind("tcp://*:4000");

    while (true) {
        std::cout << "Waiting for msg" << std::endl; 

        std::vector<zmq::message_t> recv_msgs;
        zmq::recv_multipart(server, std::back_inserter(recv_msgs));

        std::string received_message(static_cast<char*>(recv_msgs[1].data()), recv_msgs[1].size());
        std::cout << "Received message: " << received_message << std::endl;

        server.send(recv_msgs[0], zmq::send_flags::sndmore);
        server.send(zmq::message_t(), zmq::send_flags::sndmore);
        server.send(zmq::buffer("Response from server"), zmq::send_flags::none);
    }

    return 0;
}

client.cpp

#include <zmq.hpp>
#include <zmq_addon.hpp>
#include <array>
#include <iostream>
#include <string>

int main() {
    zmq::context_t context(1);
    zmq::socket_t client(context, zmq::socket_type::router);
    client.set(zmq::sockopt::routing_id, "client");
    client.connect("tcp://127.0.0.1:4000");

    // Send a request to the server
    std::array<zmq::const_buffer, 3> bufs = {
      zmq::buffer("server"), // set identity of server we try to reach
      zmq::str_buffer(""),
      zmq::str_buffer("Hello, Server!")
    };
    zmq::send_multipart(client, bufs);

    // Receive the response
    zmq::message_t reply;
    zmq::message_t identity;
    client.recv(identity, zmq::recv_flags::none);
    client.recv(reply, zmq::recv_flags::none);

    std::string reply_message(static_cast<char*>(reply.data()), reply.size());
    std::cout << "Received reply: " << reply_message << std::endl;

    return 0;
}
  • I read the guide
  • I checked for similar issues on github
  • I tried several variations of identities, buffers, multipart messages, and socket types (as a sanity check)

Solution

  • Modify your client.cpp according to the following patch:

    --- client1.cc  2024-08-18 21:07:00.000000000 +0000
    +++ client2.cc  2024-08-18 21:08:00.000000000 +0000
    @@ -1,18 +1,21 @@
     #include <zmq.hpp>
     #include <zmq_addon.hpp>
     #include <array>
    +#include <chrono>
     #include <iostream>
     #include <string>
    +#include <thread>
     
     int main() {
         zmq::context_t context(1);
         zmq::socket_t client(context, zmq::socket_type::router);
         client.set(zmq::sockopt::routing_id, "client");
         client.connect("tcp://127.0.0.1:4000");
    +    std::this_thread::sleep_for(std::chrono::milliseconds{100});
     
         // Send a request to the server
         std::array<zmq::const_buffer, 3> bufs = {
    -      zmq::buffer("server"), // set identity of server we try to reach
    +      zmq::str_buffer("server"), // set identity of server we try to reach
           zmq::str_buffer(""),
           zmq::str_buffer("Hello, Server!")
         };
    

    There are two changes:

    • I added a small delay after connecting. This is neccessary as ZeroMQ works asynchronously and does work in a background thread. Therefore the connection is not guaranteed to be established when client.connect(...) returns. A small delay ensures the connection is established and the message gets delivered.
    • I changed the first part of the multi-part message to str_buffer.

    Regarding the connection: Relaying on a delay is racy and not ideal. A better solution would probably be to monitor the connection state via zmq_socket_monitor (or it's cppzmq equivalent) and wait for ZMQ_EVENT_CONNECTED. I would hope this event gets delivered after the connection is fully established (the routing IDs exchanged), but I'm not sure. Worth a try.