zeromq:重置REQ / REP套接字状态

时间:2014-11-13 17:57:11

标签: c++ sockets zeromq

当您使用简单的ZeroMQ REQ / REP模式时,您依赖于固定的send() - > recv()/ recv() - > send()序列。 正如this文章描述的那样,当参与者在请求中间断开连接时会遇到麻烦,因为那时你不能重新开始接收来自另一个连接的下一个请求,但状态机会强制你发送请求断开连接的请求。

自上述文章撰写以来,是否有更优雅的方法来解决这个问题?

重新连接解决此问题的唯一方法(除了不使用REQ / REP但使用其他模式)

5 个答案:

答案 0 :(得分:21)

作为公认的答案似乎很可怕伤心对我来说,我做了一些研究,发现这一切我们需要的实际上是文档。

带有正确参数的 .setsockopt() 可以帮助您重置套接字状态机,而不会残酷地销毁它,并在前一个尸体上重建另一个。

(是的,我喜欢这个形象)。

  

ZMQ_REQ_CORRELATE: 将回复与请求进行匹配   REQ套接字的默认行为是依赖于消息的排序来匹配请求和响应,这通常就足够了。当此选项设置为1时,REQ套接字将使用包含请求 id 的额外框架为外发邮件添加前缀。这意味着完整的消息是(请求ididentity0user frames…)。 REQ套接字将丢弃所有不以这两个帧开头的传入消息   选项值类型int
  选项值单位01
  默认值0
  适用的插座类型 ZMQ_REQ

   ZMQ_REQ_RELAXED: 放宽请求和回复之间的严格更改   默认情况下,REQ套接字不允许使用zmq_send(3)发起新请求,直到收到对前一个请求的回复为止。设置为 1 时,允许发送另一条消息,并且具有断开与期望回复的对等方的基础连接的效果,从而触发对支持它的传输的重新连接尝试。重置请求 - 回复状态机,并向下一个可用对等体发送新请求   如果设置为1,还要启用 ZMQ_REQ_CORRELATE 以确保请求和回复的正确匹配。否则,可以将对已中止请求的延迟回复报告为对取代请求的回复   选项值类型int
  选项值单位01
  默认值0
  适用的套接字类型 ZMQ_REQ

A complete documentation is here

答案 1 :(得分:6)

好消息是,从ZMQ 3.0及更高版本(现代)开始,您可以在套接字上设置超时。正如其他人在其他地方所指出的那样,您必须在创建套接字之后但在连接之前执行此操作:

zmq_req_socket.setsockopt( zmq.RCVTIMEO, 500 ) # milliseconds

然后,当您实际尝试接收回复时(在向REP套接字发送消息之后),您可以捕获在超出超时时将断言的错误:

 try:
   send( message, 0 )
   send_failed = False

 except zmq.Again:
   logging.warning( "Image send failed." )
   send_failed = True

然而!当发生这种情况时,正如在别处观察到的那样,你的套接字将处于一个有趣的状态,因为它仍然会期待响应。此时,除了重新启动套接字之外,我找不到任何可靠的工作方式。请注意,如果断开()套接字然后重新连接()它,它仍将处于这种不良状态。因此你需要

def reset_my_socket:
  zmq_req_socket.close()
  zmq_req_socket = zmq_context.socket( zmq.REQ )
  zmq_req_socket.setsockopt( zmq.RCVTIMEO, 500 ) # milliseconds
  zmq_req_socket.connect( zmq_endpoint )

您还会注意到,因为我关闭()d套接字,接收超时选项“丢失”,所以在新套接字上设置它是很重要的。

我希望这会有所帮助。我希望这不会成为这个问题的最佳答案。 :)

答案 2 :(得分:1)

这有一个解决方案,即为所有呼叫添加超时。由于ZeroMQ本身并不真正提供简单的超时功能,我建议使用ZeroMQ套接字的子类,为所有重要的调用添加超时参数。

因此,不是调用s.recv()而是调用s.recv(timeout = 5.0),如果响应在5秒钟内没有返回,它将返回None并停止阻塞。当我遇到这个问题时,我对此做了一个徒劳的尝试。

答案 3 :(得分:1)

我此刻正在研究这个问题,因为我对传统系统进行了改造。

我经常遇到代码,“需要”了解连接状态。但问题是我想转向图书馆宣传的传递范式。

我找到了以下功能:zmq_socket_monitor

它的作用是监视传递给它的套接字并生成事件然后传递给“inproc”端点 - 此时你可以添加处理代码来实际执行某些操作。

此处还有一个示例(实际上是测试代码):github

我目前还没有任何特定的代码(可能在本周末),但我的目的是响应连接和断开连接,以便我可以实际执行所需的逻辑重置。

希望这会有所帮助,尽管引用了4.2文档,但我使用的是4.0.4,它似乎具有该功能 同样。

注意我注意到你在谈论上面的python,但问题是标记为C ++,所以这就是我的答案来自......

答案 4 :(得分:0)

这里的答案似乎都不是准确或有用的。 OP不在寻找有关BSD套接字编程的信息。他正在尝试找出如何在ZMQ中强健处理 accept()客户端套接字故障,以防止服务器挂起或崩溃的方法。

如前所述-ZMQ试图假装服务器的listen()套接字与accept()套接字是相同的,因此这个问题变得很复杂(并且文档中没有任何地方描述如何在此类套接字上设置基本超时。)

我的答案

在深入研究了代码之后,传递给accept()袜子的唯一相关套接字选项似乎是父listen()er的保持活动选项。因此解决方案是在调用send或recv之前在侦听套接字上设置以下选项:

void zmq_setup(zmq::context_t** context, zmq::socket_t** socket, const char* endpoint)
{
    // Free old references.
    if(*socket != NULL)
    {
        (**socket).close();
        (**socket).~socket_t();
    }
    if(*context != NULL)
    {
        // Shutdown all previous server client-sockets.
        zmq_ctx_destroy((*context));
        (**context).~context_t();
    }

    *context = new zmq::context_t(1);
    *socket = new zmq::socket_t(**context, ZMQ_REP);

    // Enable TCP keep alive.
    int is_tcp_keep_alive = 1;
    (**socket).setsockopt(ZMQ_TCP_KEEPALIVE, &is_tcp_keep_alive, sizeof(is_tcp_keep_alive));

    // Only send 2 probes to check if client is still alive.
    int tcp_probe_no = 2;
    (**socket).setsockopt(ZMQ_TCP_KEEPALIVE_CNT, &tcp_probe_no, sizeof(tcp_probe_no));

    // How long does a con need to be "idle" for in seconds.
    int tcp_idle_timeout = 1;
    (**socket).setsockopt(ZMQ_TCP_KEEPALIVE_IDLE, &tcp_idle_timeout, sizeof(tcp_idle_timeout));

    // Time in seconds between individual keep alive probes.
    int tcp_probe_interval = 1;
    (**socket).setsockopt(ZMQ_TCP_KEEPALIVE_INTVL, &tcp_probe_interval, sizeof(tcp_probe_interval));

    // Discard pending messages in buf on close.
    int is_linger = 0;
    (**socket).setsockopt(ZMQ_LINGER, &is_linger, sizeof(is_linger));

    // Start internal enclave event server.
    printf("Host: Starting enclave event server\n");
    (**socket).bind(endpoint);
}

这是要告诉操作系统积极检查客户端套接字是否超时,并在客户端未及时返回心跳时重新获取它们以进行清理。结果是操作系统将SIGPIPE发送回您的程序,套接字错误将冒泡发送/接收-修复挂起的服务器。然后,您需要再做两件事:

1。处理SIGPIPE错误,以使程序不会崩溃

#include <signal.h>
#include <zmq.hpp>

// zmq_setup def here [...]

int main(int argc, char** argv) 
{
    // Ignore SIGPIPE signals.
    signal(SIGPIPE, SIG_IGN);
    // ... rest of your code after

    // (Could potentially also restart the server
    // sock on N SIGPIPEs if you're paranoid.)

    // Start server socket.
    const char* endpoint = "tcp://127.0.0.1:47357";
    zmq::context_t* context;
    zmq::socket_t* socket;
    zmq_setup(&context, &socket, endpoint);

    // Message buffers.
    zmq::message_t request;
    zmq::message_t reply;

    // ... rest of your socket code here
}

2。检查send或recv返回的-1并捕获ZMQ错误。

// E.g. skip broken accepted sockets (pseudo-code.)
while (1):
{
    try
    {
        if ((*socket).recv(&request)) == -1)
            throw -1;
    }
    catch (...)
    {
        // Prevent any endless error loops killing CPU.
        sleep(1)

        // Reset ZMQ state machine.
        try
        {
            zmq::message_t blank_reply = zmq::message_t();
            (*socket).send (blank_reply);
        }
        catch (...)
        {
            1;
        } 

        continue;
    }

注意到在套接字失败时尝试发送答复的奇怪代码吗?在ZMQ中,REP服务器“套接字”是另一个程序的端点,该程序在该服务器上建立了REQ套接字。结果是,如果您使用挂起的客户端在REP套接字上执行recv,服务器袜子将陷入一个中断的接收循环中,它将永远等待接收有效的答复。

要在状态机上强制进行更新,请尝试发送答复。 ZMQ检测到套接字已损坏,并将其从队列中删除。服务器套接字变为“未塞住”,下一个recv调用从队列中返回一个新客户端。

要在异步客户端上启用超时(在 Python 3 中),代码应如下所示:

import asyncio
import zmq
import zmq.asyncio

@asyncio.coroutine
def req(endpoint):
    ms = 2000 # In milliseconds.
    sock = ctx.socket(zmq.REQ)
    sock.setsockopt(zmq.SNDTIMEO, ms)
    sock.setsockopt(zmq.RCVTIMEO, ms)
    sock.setsockopt(zmq.LINGER, ms) # Discard pending buffered socket messages on close().
    sock.setsockopt(zmq.CONNECT_TIMEOUT, ms)

    # Connect the socket.
    # Connections don't strictly happen here.
    # ZMQ waits until the socket is used (which is confusing, I know.)
    sock.connect(endpoint)

    # Send some bytes.
    yield from sock.send(b"some bytes")

    # Recv bytes and convert to unicode.
    msg = yield from sock.recv()
    msg = msg.decode(u"utf-8")  

现在,当出现问题时,您会遇到一些失败的情况。

顺便说一句-如果有人好奇的话-Linux中TCP空闲超时的默认值似乎是7200秒或2小时。因此,您将等待很长的时间让挂起的服务器执行任何操作!

来源:

免责声明:

我已经测试了这段代码,并且似乎可以正常工作,但是ZMQ确实使测试变得复杂了很多,因为客户端在失败时重新连接了吗?如果有人想在生产中使用此解决方案,我建议首先编写一些基本的单元测试。

服务器代码还可以通过线程或轮询进行大量改进,以便能够一次处理多个客户端。就目前而言,恶意客户端可能会暂时从服务器上占用资源(3秒超时),这是不理想的。