ZeroMQ是否有数据到达时的通知/回调事件/消息?

时间:2014-11-21 20:02:02

标签: c++ multithreading sockets mfc zeromq

我正在尝试将ZMQ集成到现有的Windows应用程序中,该应用程序严重依赖于MFC套接字(CASyncSocket)。

我有一个CWinThread派生的UI线程(没有GUI),它使用CAsyncSocket异步与服务器通信。我想添加一个ZMQ inproc通信线来处理从服务器接收的数据(基于REQ / REP)与应用程序中的其他线程的通信。

使用CAsyncSocket时,每当要接收的套接字上有新数据时,MFC框架就会调用OnReceive方法(这可能是对那里的硬核MFC专家的过度简化)。

ZMQ中是否有这样的机制?或者我是否必须添加一个额外的专用WorkerThread,UI线程启动以处理我与应用程序其余部分的ZMQ通信?两个管道上的流量都很小,所以我真的不想创建2个单独的线程,如果我可以使用1。

注意,我已经掌握了基础知识,我只是遇到了同步问题。如果我使用阻塞recv / send与ZMQ,它会破坏我的CAsycSocket,因为Windows消息永远不会被线程处理,导致有时永远不会从ZMQ应该传递的服务器获取数据。但是如果我使用非阻塞的ZMQ调用,那么线程经常最终处于空闲状态,因为它不知道要读取ZMQ套接字。

3 个答案:

答案 0 :(得分:1)

最终,答案是。当数据到达您可以链接到的ZeroMQ时,没有当前的回调/通知。我也找不到任何增加此功能的分支。

当使用MFC套接字框架在单个线程中提供的传统OnReceive调用并且添加第二个线程专用于ZMQ时,我无法使ZMQ工作,从而打败了使用它的整个目的(它用于线程同步) )。

我的实现最终会丢弃MFC套接字并将ZMQ用于我的inproc服务器(用于与其他线程通信)以及我的TCP(非ZMQ)服务器连接并使用阻塞轮询调用(zmq_poll())在OnIdle()方法中(每次返回1以创建一个忙循环)。阻止民意调查

BOOL CMyThreaClass::OnIdle(LONG lCount)
{
    UNREFERENCED_PARAMETER(lCount);

    zmq_pollitem_t items [] = {
    { m_pZMQInprocServer, 0, ZMQ_POLLIN, 0 },
    { m_pZMQTCPSocket, 0, ZMQ_POLLIN, 0 }
    };
    const int iZMQInfiniteTimeout(-1);
    iResult = zmq_poll(&items[0], sizeof(items) / sizeof(items[0]), iZMQInfiniteTimeout);
    TRACE("zmq_poll result: %d\n", iResult);

    if (items[0].revents & ZMQ_POLLIN)
    {
        sMyStruct sMessage;
        iResult = zmq_recv(m_pZMQInprocServer, &sMessage, sizeof(sMessage), ZMQ_DONTWAIT); // don't block (the zmq_poll blocks for us)
        TRACE("inproc recv result: %d\n", iResult);
        //  Process inproc messages
        iResult = zmq_send(pZMQInprocServer, &sMessage, sizeof(sMessage), ZMQ_NULL); // block
        TRACE("inproc send result: %d\n", iResult);
    }
    if (items[1].revents & ZMQ_POLLIN)
    {
        // there will be an ZMQ_IDENTITY identifier on the beginning of the socket buffer, read it off first
        uint8_t id [256];
        size_t id_size = 256;
        iResult = zmq_getsockopt(m_pZMQTCPSocket, ZMQ_IDENTITY, id, &id_size);
        TRACE("getsockopt poll result %d:id %d\n", iResult, id);
        iResult = zmq_recv(m_pZMQTCPSocket, &id, id_Size, ZMQ_DONTWAIT); // don't block
        // now get our actual data
        char szBuffer[1024];
        int iBytesReceived = zmq_recv(m_pZMQSocket, szBuffer, sizeof(szBuffer), ZMQ_DONTWAIT);
        if (iBytesReceived > 0)
        {
            // process TCP data
        }
    }
}

注意:此答案需要使用ZMQ 4或更高版本,因为早期版本的ZMQ无法与常规TCP套接字连接进行通信。

答案 1 :(得分:0)

您可以通过覆盖zmq_recv()在应用主消息循环中使用ZMQ_NOBLOCK标记来呼叫CWinApp::OnIdle()。如果套接字上没有数据等待,zmq_recv将立即返回。如果有数据,请处理它 - 但要注意,如果你做得很慢,你会让应用程序无响应。

编辑:我没有意识到OnIdle只在消息队列变空时才被调用一次。但是根据MSDN's documentation,您可以返回非零值以永久调用:

  1. 如果消息循环检查消息队列并且未发现待处理消息,则会调用OnIdle并提供0作为lCount参数。
  2. OnIdle执行一些处理并返回非零值,表示应再次调用它以进行进一步处理。
  3. 消息循环再次检查消息队列。如果没有待处理的消息,则再次调用OnIdle,递增lCount参数。
  4. 最终,OnIdle完成处理所有空闲任务并返回0。这告诉消息循环停止调用OnIdle,直到从消息队列收到下一条消息,此时空闲循环重新开始,参数设置为0
  5. 我还发现this thread on GameDev.net用户说:

      

    我在MFC中使用D3D编写的所有工具都使用OnIdle()函数:

    BOOL CD3DEditorApp::OnIdle(LONG lCount) 
    {
        // Call base class first
        CWinApp:OnIdle( lCount );
    
        // game stuff...
    
        // Always return true - this asks the framework to constantly
        // call the Idle function when it isn't busy doing something
        // else.
        return TRUE;
    }
    

    所以,至少据一个人说,这是一种常见的技术。

答案 2 :(得分:0)

您可以使用自定义Windows消息从线程发送信号。这是一条自定义消息:

#define WM_MY_MESSAGE (WM_APP + 1)

将其发送到具有窗口的线程使用PostMessage或SendMessage到HWND。使用ON_MESSAGE将其添加到窗口的消息映射中。

将它发送到没有窗口的CWinThread派生线程使用PostThreadMessage并使用ON_THREAD_MESSAGE接收它。