问题
I am trying to integrate ZMQ into an existing windows application that relies heavily on MFC sockets (CASyncSocket).
I've got a CWinThread derived UI thread (without a GUI) that communicates with a server asynchronously using CAsyncSocket. I would like to add a ZMQ inproc communication line to handle communicating the data received from the server (on a REQ/REP basis) to other threads within the application.
Using CAsyncSocket, the OnReceive method is called by the MFC framework whenever new data is available on the socket to be received (that might be an over-simplification to the hardcore MFC gurus out there).
Is there any such mechanism in ZMQ? Or do I have to add an additional dedicated WorkerThread that the UI thread launches to handle my ZMQ communications to the rest of the app? The traffic on both pipelines is minimal so I really don't want to have to create 2 separate threads if I can get by with 1.
Note, I've got the basics working, I'm just having problems with synchronization. If I use blocking recv/send with ZMQ, it starves out my CAsycSocket because the windows messages never get processed by the thread resulting in sometimes never getting the data from the server that the ZMQ is supposed to be delivering. But if I use non-blocking ZMQ calls, then the thread frequently ends up sitting idle because it doesn't know to read off the ZMQ socket.
回答1:
Ultimately, the answer is no. There are no current callback/notifications for when data arrives in ZeroMQ that you can link into. I was also unable to find any fork that adds this functionality.
I was unable to get ZMQ working while using the traditional OnReceive calls provide by the MFC socket framework within a single thread and adding a 2nd thread to dedicate to ZMQ defeated the whole purpose for using it (it is being used for thread synchronization).
My implementation that works ended up dropping MFC sockets and using ZMQ for both my inproc server (for communicating with other thread) as well as my TCP (non-ZMQ) server connection and using a blocking polling call (zmq_poll()) in the OnIdle() method (returning 1 every time to create a busy loop). The blocking poll
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
}
}
}
Note: This answer requires using ZMQ 4 or later since earlier versions of ZMQ will not communicate with a regular TCP socket connection.
回答2:
You could call zmq_recv()
with the ZMQ_NOBLOCK
flag inside your app's main message loop by overriding your CWinApp::OnIdle()
. zmq_recv
will return immediately if there is no data waiting on the socket. If there's data, handle it - but be aware that if you do something slow, you'll make the app unresponsive.
Edit: I didn't realize that OnIdle
only gets called once when the message queue becomes empty. But according to MSDN's documentation, you can return a nonzero value to keep getting called forever:
- If the message loop checks the message queue and finds no pending messages, it calls
OnIdle
and supplies0
as thelCount
argument. OnIdle
performs some processing and returns a nonzero value to indicate it should be called again to do further processing.- The message loop checks the message queue again. If no messages are pending, it calls
OnIdle
again, incrementing thelCount
argument. - Eventually,
OnIdle
finishes processing all its idle tasks and returns0
. This tells the message loop to stop callingOnIdle
until the next message is received from the message queue, at which point the idle cycle restarts with the argument set to0
.
I also found this thread on GameDev.net where a user said:
All of the tools I have ever written in MFC that use D3D use the OnIdle() function:
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;
}
So, at least according to one person, it's a common technique.
回答3:
You can signal from thread to thread with custom Windows messages. Here is a custom message:
#define WM_MY_MESSAGE (WM_APP + 1)
To send it to a thread that has a window use PostMessage or SendMessage to the HWND. Add it to the window's message map with ON_MESSAGE.
To send it to a CWinThread-derived thread that has no windows use PostThreadMessage and receive it with ON_THREAD_MESSAGE.
来源:https://stackoverflow.com/questions/27069359/does-zeromq-have-a-notification-callback-event-message-for-when-data-arrives