ZeroMQ(cppzmq)订阅者跳过第一条消息

时间:2017-08-17 16:11:20

标签: c++ zeromq

我尝试将ZMQCPPZMQ C ++包装器一起使用,因为它似乎是C++ Bindings中建议的那个。

客户端/服务器(REQ / REP)似乎工作正常。 在尝试实现发布/订阅程序对时,看起来第一条消息在订户中丢失。为什么呢?

publisher.cpp:

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread/thread.hpp>
#include <boost/format.hpp>
#include <zmq.hpp>
#include <string>
#include <iostream>

int main()
{
    zmq::context_t context(1);
    zmq::socket_t publisher(context, ZMQ_PUB);
    publisher.bind("tcp://*:5555");

    for(int n = 0; n < 3; n++) {
        zmq::message_t env1(1);
        memcpy(env1.data(), "A", 1);
        std::string msg1_str = (boost::format("Hello-%i") % (n + 1)).str();
        zmq::message_t msg1(msg1_str.size());
        memcpy(msg1.data(), msg1_str.c_str(), msg1_str.size());
        std::cout << "Sending '" << msg1_str << "' on topic A" << std::endl;
        publisher.send(env1, ZMQ_SNDMORE);
        publisher.send(msg1);

        zmq::message_t env2(1);
        memcpy(env2.data(), "B", 1);
        std::string msg2_str = (boost::format("World-%i") % (n + 1)).str();
        zmq::message_t msg2(msg2_str.size());
        memcpy(msg2.data(), msg2_str.c_str(), msg2_str.size());
        std::cout << "Sending '" << msg2_str << "' on topic B" << std::endl;
        publisher.send(env2, ZMQ_SNDMORE);
        publisher.send(msg2);

        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
    }
    return 0;
}

subscriber.cpp:

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

int main()
{
    zmq::context_t context(1);
    zmq::socket_t subscriber(context, ZMQ_SUB);
    subscriber.connect("tcp://localhost:5555");
    subscriber.setsockopt(ZMQ_SUBSCRIBE, "B", 1);

    while(true)
    {
        zmq::message_t env;
        subscriber.recv(&env);
        std::string env_str = std::string(static_cast<char*>(env.data()), env.size());
        std::cout << "Received envelope '" << env_str << "'" << std::endl;

        zmq::message_t msg;
        subscriber.recv(&msg);
        std::string msg_str = std::string(static_cast<char*>(msg.data()), msg.size());
        std::cout << "Received '" << msg_str << "'" << std::endl;
    }
    return 0;
}

节目输出:

$ ./publisher
Sending 'Hello-1' on topic A
Sending 'World-1' on topic B
Sending 'Hello-2' on topic A
Sending 'World-2' on topic B
Sending 'Hello-3' on topic A
Sending 'World-3' on topic B

$ ./subscriber
Received envelope 'B'
Received 'World-2'
Received envelope 'B'
Received 'World-3'

(注意:订阅者在执行发布者之前执行)

奖金问题:顺便说一句,这是我的印象还是这个C ++包装器它的级别很低?我看不到对std :: string的直接支持,传输简单字符串的代码看起来相当冗长。

2 个答案:

答案 0 :(得分:2)

the ZeroMQ Guide中找到答案:

  

有一个关于PUB-SUB插座的重要事项:你   不知道用户何时开始收到消息。甚至   如果您启动订阅者,请等待一段时间,然后启动发布者,   订阅者将始终错过发布者的第一封邮件   的发送即可。这是因为订阅者连接到发布者   (出版商可能需要很小但非零的时间)   已经发送消息了。

     

这个&#34;慢木匠&#34;症状经常足以引起我们的注意   要详细解释它。请记住,ZeroMQ是异步的   I / O,即在后台。假设您有两个节点在执行此操作   这个顺序:

     

订阅者连接到端点并接收和计算消息。   Publisher绑定到端点并立即发送1,000条消息。   然后订户很可能不会收到任何东西。你&#39; 11   眨眼,检查您是否设置了正确的过滤器,然后重试,并且   订阅者仍然不会收到任何内容。

     

建立TCP连接涉及到握手和来自握手   几毫秒,具体取决于您的网络和跃点数   同伴之间。在那段时间,ZeroMQ可以发送许多消息。为了清酒   参数假设建立连接需要5毫秒,而且   相同的链接每秒可以处理1M条消息。在5 msecs期间   订阅者正在连接到发布者,它需要   发布者只需1毫秒即可发送这些1K消息。

     

Chapter 2 - Sockets and Patterns中,我们将解释如何同步a   发布商和订阅者,以便您不会开始发布数据   直到订阅者真正连接并准备好。有一个   简单而愚蠢的方式来延迟发布者,这是睡觉。唐&#39;吨   但是,在实际的应用程序中这样做,因为它非常脆弱   以及不优雅和缓慢。用睡眠来证明自己是什么样的   发生,然后等待Chapter 2 - Sockets and Patterns看到   怎么做对。

     

同步的替代方法是简单地假设   发布的数据流是无限的,没有开始也没有结束。一   还假设订户不关心之前发生的事情   它开始了。这就是我们构建天气客户端示例的方式。

     

因此,客户订阅其选择的邮政编码并收集100   该邮政编码的更新。这意味着大约有一千万次更新   服务器,如果邮政编码是随机分布的。你可以开始了   客户端,然后是服务器,客户端将继续工作。您可以   根据需要随时停止并重新启动服务器,客户端将会   继续工作。当客户收集了一百个更新时,它   计算平均值,打印并退出。

答案 1 :(得分:1)

奖金回答:

ZeroMQ专为高性能消息/信令而设计,因此具有一些设计准则,围绕这些设计准则开发了核心部分。

Zero-Copy和Zero-Sharing是那些比较知名的,零(几乎) - Latency可能(有点)挑衅,而Zero-Warranty可能是一个,你最不愿意听到的

是的,ZeroMQ不会努力提供任何明确的保证(当然,由于分布式系统世界中常见的许多原因),但它为您提供了这种保修 - 任何消息都是以原子方式传递的(即完整的,无错误的) - 或根本不传递(因此,确实无需支付任何额外费用,与检测和丢弃任何欠幅和/或损坏的消息 - 有效负载相关联)

所以可能宁可忘记担心任何未送达的包裹,以及如果这些包裹被送达等等。您只需尽可能多地获得,其余的不受您的影响(&#34; Late-joiner&#34;案件可以被视为一个边界,其中(如果)一个人处于这样一个位置,以便能够为“慢慢连接者”执行更多时间,那么没有任何这种可观察到的差异会改变代码 - 设计,所以试图设计分布式系统以对抗(主要)可能未传送的信号/消息。

API?包装...

如果对这个详细程度感兴趣,会建议阅读API,因为有些v2.x,这样一个人可能会更好地实现所有的想法,那就是为了获得最大性能而付诸实践(Zero-Copy动机集)消息准备步骤,消息的高级API调用,将被重新发送,内存泄漏防护,高级IO线程池映射以增加IO吞吐量/减少延迟/相对优先级等等。) p>

在此之后,人们可以回顾一下任何相应的非本地语言绑定(包装器)在跨端口编程环境中如何将这些初始设计工作反映出来(或者有多差)。

大多数此类努力都在寻找用户编程舒适度,目标编程环境表达性约束和最小化泄漏内存或API绑定/包装器质量受损之间的合理平衡时遇到了麻烦。

值得注意的是,设计非母语语言绑定是一些最具挑战性的任务之一。因此,应该选择进入这一领域的这些勇敢的团队(有时未能反映所有本机API优势而不降低性能和/或原始意图的清晰度 - 无需添加,许多本机API功能可能即使被排除在环境之外也无法访问,无法在这种非本地语言表达的范围内提供无缝集成,因此在评估API绑定/包装器时要小心(原始的本机API总是有助于获取无论如何 - 在大多数极端情况下,人们可能会尝试在关键部分内联)。