ZeroMQ是否专为单向通信而设计?

时间:2014-10-16 09:23:48

标签: c++ multithreading zeromq

ZeroMQ的大多数示例和测试都在一个方向上使用通道。显然,一些ZeroMQ模式可以用于双向通信,但这会带来性能成本吗?

我创建了一个示例来说明我们计划如何使用ZeroMQ,完全异步,客户端和服务器可以随时从任何线程发送请求/回复。我试图尽可能简单,但请尽量忽略错误处理和关机过程。 请评论任何其他问题,使用您可能找到的代码

客户端可以运行方式,可以是同步的,在哪里序列化请求,也可以是异步的,它会创建一个单独的线程来尽快发送请求。请求很小,只是创建请求时的时间戳和一个简单的字符串。服务器接收请求,添加更多字符串数据并简单地返回答复。然后,客户端在回复中提取时间戳并计算往返时间。

现在,当以异步模式运行时,在收到几乎所有回复之前,不会完成发送线程。 为什么这样,我期待单独的发送线程更快完成?

RTT时间相当高。

在异步模式下,可以通过为每150个请求添加 sleep(1) 来改进RTT,而不会花费太多时间发送/接收所有消息。 这是否表明ZeroMQ没有有效处理队列?

在同步模式下,RTT虽然较低,但仍然非常高,只需处理一个请求,然后影响总吞吐量。

在Windows 8工作站上本地运行时的输出示例:

Send thread finished, 100000 requests sent in us=1047525  
Received 100000 replies, last 10000 took us=95440, averate RTT=36228  
Work thread finished, 100000 replies received in us=1077641  

下面是客户端和服务器的代码。

TIA

客户代码:

// ZMQClient.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <profileapi.h>
#include <atomic>
#include <handleapi.h>
#include <process.h>
#include "zmq.hpp"
#include <queue>


using namespace zmq;
using namespace std;

//#define SYNCHRONOUS

namespace
{
    LARGE_INTEGER ui64StartCounter, ui64StopCounter, Frequency;
    HANDLE g_zmqThread = INVALID_HANDLE_VALUE;
    HANDLE g_workThread = INVALID_HANDLE_VALUE;
    HANDLE g_sendThread = INVALID_HANDLE_VALUE;
    SRWLOCK g_queueLock;
    queue<message_t*> g_incomingQueue;
    HANDLE g_queueUpdated = INVALID_HANDLE_VALUE;
    bool g_runThreads = true;

    context_t g_context(1);

    const unsigned int g_numOfIterations = 100000;
    const unsigned int g_statisticsInterval = 10000;
}



static DWORD WINAPI ZmqThreadStatic(PVOID pThreadParameter)
{
    if (pThreadParameter == nullptr) return ERROR_INVALID_PARAMETER;

    PCSTR connectionAddress = (PCSTR)pThreadParameter;
    socket_t sendSocket(g_context, ZMQ_PULL);
    sendSocket.bind("inproc://sendsocket");

    printf("Connecting to: %s\n", connectionAddress);

    zmq::socket_t clientSocket(g_context, ZMQ_DEALER);
    clientSocket.connect(connectionAddress);

    zmq_pollitem_t pollItems[] = {
        { sendSocket, 0, ZMQ_POLLIN, 0 },
        { clientSocket, 0, ZMQ_POLLIN, 0 }
    };

    message_t zMsgt;
    int more;
    size_t size = sizeof(more);
    while (g_runThreads)
    {
        zmq_poll(pollItems, 2, -1);

        if (pollItems[0].revents & ZMQ_POLLIN)
        {
            while (true)
            {
                sendSocket.recv(&zMsgt, 0);
                sendSocket.getsockopt(ZMQ_RCVMORE, &more, &size);
                clientSocket.send(zMsgt, more ? ZMQ_SNDMORE : 0);
                if (more == 0) break;
            }
        }
        if (pollItems[1].revents & ZMQ_POLLIN)
        {
            while (true)
            {
                clientSocket.recv(&zMsgt, 0);

                message_t* newMsg = new message_t;
                newMsg->move(&zMsgt);
                AcquireSRWLockExclusive(&g_queueLock);
                g_incomingQueue.push(newMsg);
                ReleaseSRWLockExclusive(&g_queueLock);
                SetEvent(g_queueUpdated);

                clientSocket.getsockopt(ZMQ_RCVMORE, &more, &size);
                if (more == 0) break;
            }
        }
    }

    clientSocket.close();

    printf("ZMQ thread finished\n");

    return NO_ERROR;
}

static DWORD WINAPI WorkThreadStatic(PVOID pThreadParameter)
{
    socket_t sendSocket(g_context, ZMQ_PUSH);
    sendSocket.connect("inproc://sendsocket");

    LARGE_INTEGER ui64StartCounter, ui64StopCounter, ui64TotalCounter, ui64RTTCounter, Frequency;
    QueryPerformanceFrequency((PLARGE_INTEGER)&Frequency);
    ui64TotalCounter.QuadPart = 0;
    ui64RTTCounter.QuadPart = 0;
    unsigned int iterations = 0;

#ifdef SYNCHRONOUS
    LARGE_INTEGER ui64CurrentCounter;
    printf("Work thread sending synchronous requests...\n");
    string dataStr = "Hello World, " + to_string(iterations);
    QueryPerformanceCounter(&ui64CurrentCounter);
    message_t outMsg(sizeof(ui64CurrentCounter) + dataStr.size());
    memcpy(outMsg.data(), &ui64CurrentCounter, sizeof(ui64CurrentCounter));
    memcpy((char*)outMsg.data() + sizeof(ui64CurrentCounter), dataStr.c_str(), dataStr.size());
    sendSocket.send(outMsg, 0);
#endif
    bool isWorking = true;
    while (isWorking && g_runThreads)
    {
        switch (WaitForSingleObjectEx(g_queueUpdated, INFINITE, FALSE))
        {
            case WAIT_OBJECT_0:
                while (!g_incomingQueue.empty())
                {
                    AcquireSRWLockExclusive(&g_queueLock);
                    message_t* msg = g_incomingQueue.front();
                    g_incomingQueue.pop();
                    ReleaseSRWLockExclusive(&g_queueLock);

                    if (iterations == 0)
                    {
                        printf("First request, starting timer...\n");
                        QueryPerformanceCounter(&ui64StartCounter);
                    }

                    QueryPerformanceCounter(&ui64StopCounter);
                    LARGE_INTEGER* sendTime = (LARGE_INTEGER*)msg->data();
                    ui64StopCounter.QuadPart -= sendTime->QuadPart;

                    ui64RTTCounter.QuadPart += ui64StopCounter.QuadPart;
                    delete msg;

                    if (++iterations % g_statisticsInterval == 0)
                    {
                        QueryPerformanceCounter(&ui64StopCounter);
                        ui64StopCounter.QuadPart -= ui64StartCounter.QuadPart;
                        ui64StopCounter.QuadPart *= 1000000;
                        ui64StopCounter.QuadPart /= Frequency.QuadPart;
                        ui64RTTCounter.QuadPart /= 10000;
                        ui64RTTCounter.QuadPart *= 1000000;
                        ui64RTTCounter.QuadPart /= Frequency.QuadPart;
                        printf("Received %d replies, last %d took us=%lu, averate RTT=%lu\n", iterations, g_statisticsInterval, ui64StopCounter.QuadPart, ui64RTTCounter.QuadPart);
                        ui64TotalCounter.QuadPart += ui64StopCounter.QuadPart;
                        ui64RTTCounter.QuadPart = 0;

                        QueryPerformanceCounter(&ui64StartCounter);
                    }
                    if (iterations >= g_numOfIterations)
                    {
                        isWorking = false;
                    }
#ifdef SYNCHRONOUS
                    else
                    {
                        string dataStr = "Hello World, " + to_string(iterations);
                        QueryPerformanceCounter(&ui64CurrentCounter);
                        message_t outMsg(sizeof(ui64CurrentCounter) + dataStr.size());
                        memcpy(outMsg.data(), &ui64CurrentCounter, sizeof(ui64CurrentCounter));
                        memcpy((char*)outMsg.data() + sizeof(ui64CurrentCounter), dataStr.c_str(), dataStr.size());
                        sendSocket.send(outMsg, 0);
                    }
#endif
                }
                break;

            default:
                isWorking = false;
                break;
        }
    }

    printf("Work thread finished, %d replies received in us=%lu\n", iterations, ui64TotalCounter.QuadPart);

    return NO_ERROR;
}

static DWORD WINAPI SendThreadStatic(PVOID pThreadParameter)
{
    socket_t sendSocket(g_context, ZMQ_PUSH);
    sendSocket.connect("inproc://sendsocket");

    LARGE_INTEGER ui64CurrentCounter, ui64StartCounter, ui64StopCounter, ui64TotalCounter, Frequency;
    QueryPerformanceFrequency((PLARGE_INTEGER)&Frequency);
    ui64TotalCounter.QuadPart = 0;

    printf("Send thread sending asynchronous requests...\n");

    unsigned int iterations;
    QueryPerformanceCounter(&ui64TotalCounter);
    QueryPerformanceCounter(&ui64StartCounter);
    for (iterations = 0; iterations < g_numOfIterations; ++iterations)
    {
        string dataStr = "Hello World, " + to_string(iterations);
        QueryPerformanceCounter(&ui64CurrentCounter);
        message_t outMsg(sizeof(ui64CurrentCounter) + dataStr.size());
        memcpy(outMsg.data(), &ui64CurrentCounter, sizeof(ui64CurrentCounter));
        memcpy((char*)outMsg.data() + sizeof(ui64CurrentCounter), dataStr.c_str(), dataStr.size());
        sendSocket.send(outMsg, 0);

        if ((iterations + 1) % g_statisticsInterval == 0)
        {
            QueryPerformanceCounter(&ui64StopCounter);
            ui64StopCounter.QuadPart -= ui64StartCounter.QuadPart;
            ui64StopCounter.QuadPart *= 1000000;
            ui64StopCounter.QuadPart /= Frequency.QuadPart;
            printf("Sent %d requests, last %d took us=%lu\n", iterations + 1, g_statisticsInterval, ui64StopCounter.QuadPart);
            QueryPerformanceCounter(&ui64StartCounter);
        }

        if ((iterations + 1) % 150 == 0)
        {
//          Sleep(1);
        }
    }
    QueryPerformanceCounter(&ui64StopCounter);
    ui64StopCounter.QuadPart -= ui64TotalCounter.QuadPart;
    ui64StopCounter.QuadPart *= 1000000;
    ui64StopCounter.QuadPart /= Frequency.QuadPart;

    printf("Send thread finished, %d requests sent in us=%lu\n", iterations, ui64StopCounter.QuadPart);

    return NO_ERROR;
}

int _tmain(int argc, _TCHAR* argv[])
{
    QueryPerformanceFrequency((PLARGE_INTEGER)&Frequency);

    g_queueUpdated = CreateEvent(nullptr, FALSE, FALSE, nullptr);

    PCTSTR serverAddress = argc > 1 ? argv[1] : _T("127.0.0.1");
    PCTSTR port = argc > 2 ? argv[2] : _T("14767");
    wstring connectionAddress = _T("tcp://");
    connectionAddress += serverAddress;
    connectionAddress += _T(":");
    connectionAddress += port;
    string address = string(connectionAddress.begin(), connectionAddress.end());

    g_zmqThread = (HANDLE)_beginthreadex(nullptr, 0, (unsigned int(__stdcall *)(PVOID)) ZmqThreadStatic, (void*)address.c_str(), 0, (unsigned *)nullptr);
    Sleep(20); // Allow the new thread to bind internal socket
    g_workThread = (HANDLE)_beginthreadex(nullptr, 0, (unsigned int(__stdcall *)(PVOID)) WorkThreadStatic, nullptr, 0, (unsigned *)nullptr);
#ifndef SYNCHRONOUS
    Sleep(20); // Allow the new thread to bind internal socket
    g_sendThread = (HANDLE)_beginthreadex(nullptr, 0, (unsigned int(__stdcall *)(PVOID)) SendThreadStatic, nullptr, 0, (unsigned *)nullptr);
#endif

    getchar();

    printf("Closing down...\n");

    g_runThreads = false;
    SetEvent(g_queueUpdated);
    CloseHandle(g_queueUpdated);
    g_queueUpdated = INVALID_HANDLE_VALUE;

    g_context.close();

    printf("Successfully shutdown.\n");

    getchar();

    return 0;
}

服务器代码:

// ZMQServer.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <profileapi.h>
#include <atomic>
#include <handleapi.h>
#include <process.h>
#include "zmq.hpp"
#include <queue>


using namespace zmq;
using namespace std;

namespace
{
    HANDLE g_zmqThread = INVALID_HANDLE_VALUE;
    HANDLE g_workThread = INVALID_HANDLE_VALUE;
    SRWLOCK g_queueLock;
    queue< pair<string, message_t*> > g_incomingQueue;
    HANDLE g_queueUpdated = INVALID_HANDLE_VALUE;
    bool g_threadsRunning = true;

    context_t g_context(1);
    context_t g_localContext(0);

    const unsigned int g_statisticsInterval = 10000;
}



static DWORD WINAPI ZmqThreadStatic(PVOID pThreadParameter)
{
    socket_t incomingSocket(g_localContext, ZMQ_DEALER);
    incomingSocket.connect("inproc://backend");

    socket_t sendSocket(g_localContext, ZMQ_PULL);
    sendSocket.connect("inproc://sendsocket");

    zmq_pollitem_t pollItems[] = {
        { sendSocket, 0, ZMQ_POLLIN, 0 },
        { incomingSocket, 0, ZMQ_POLLIN, 0 }
    };

    printf("Starting to handle incoming and outgoing data...\n");
    unsigned int iterations = 0;
    message_t zMsgt;
    int more;
    size_t size = sizeof(more);
    while (g_threadsRunning)
    {
        zmq_poll(pollItems, 2, -1);

        if (pollItems[0].revents & ZMQ_POLLIN)
        {
            while (true)
            {
                sendSocket.recv(&zMsgt, 0);
                sendSocket.getsockopt(ZMQ_RCVMORE, &more, &size);
                incomingSocket.send(zMsgt, more ? ZMQ_SNDMORE : 0);

                if (more == 0) break;
            }
        }
        if (pollItems[1].revents & ZMQ_POLLIN)
        {
            while (true)
            {
                incomingSocket.recv(&zMsgt, 0);
                string strId(static_cast<char*>(zMsgt.data()), zMsgt.size());
                incomingSocket.recv(&zMsgt);

                message_t* newMsg = new message_t;
                newMsg->move(&zMsgt);
                AcquireSRWLockExclusive(&g_queueLock);
                g_incomingQueue.push(make_pair(strId, newMsg));
                ReleaseSRWLockExclusive(&g_queueLock);
                SetEvent(g_queueUpdated);

                incomingSocket.getsockopt(ZMQ_RCVMORE, &more, &size);
                if (more == 0) break;
            }
        }
    }

    printf("Stopped handling incoming and outgoing data.\n");

    return NO_ERROR;
}

static DWORD WINAPI WorkThreadStatic(PVOID pThreadParameter)
{
    socket_t sendSocket(g_localContext, ZMQ_PUSH);
    sendSocket.bind("inproc://sendsocket");

    LARGE_INTEGER ui64StartCounter, ui64StopCounter, Frequency;
    QueryPerformanceFrequency((PLARGE_INTEGER)&Frequency);

    printf("Starting to process requests...\n");

    unsigned int iterations = 0;
    while (g_threadsRunning)
    {
        switch (WaitForSingleObjectEx(g_queueUpdated, INFINITE, FALSE))
        {
        case WAIT_OBJECT_0:
        {
            AcquireSRWLockExclusive(&g_queueLock);
            while(!g_incomingQueue.empty())
            {
                pair<string, message_t*> pair_data(g_incomingQueue.front());
                g_incomingQueue.pop();
                ReleaseSRWLockExclusive(&g_queueLock);

                string in((char*)pair_data.second->data(), pair_data.second->size());
                delete pair_data.second;

                string out = in + " - Reply";
                // This code below doesn't work when several threads are used, since the id end up in one thread and the data in another...
                message_t outMsg(out.size());
                memcpy(outMsg.data(), out.c_str(), out.size());
                message_t id(pair_data.first.size());
                memcpy(id.data(), pair_data.first.c_str(), pair_data.first.size());
                sendSocket.send(id, ZMQ_SNDMORE);
                sendSocket.send(outMsg, 0);

                if (iterations == 0)
                {
                    printf("First request, starting timer...\n");
                    QueryPerformanceCounter(&ui64StartCounter);
                }

                if (++iterations % g_statisticsInterval == 0)
                {
                    QueryPerformanceCounter(&ui64StopCounter);
                    ui64StopCounter.QuadPart -= ui64StartCounter.QuadPart;
                    ui64StopCounter.QuadPart *= 1000000;
                    ui64StopCounter.QuadPart /= Frequency.QuadPart;
                    printf("Received %d requests, last %d took us=%lu\n", iterations, g_statisticsInterval, ui64StopCounter.QuadPart);

                    QueryPerformanceCounter(&ui64StartCounter);
                }

                AcquireSRWLockExclusive(&g_queueLock);
            }
            ReleaseSRWLockExclusive(&g_queueLock);
            break;
        }
        default:
            g_threadsRunning = false;
            break;
        }
    }

    sendSocket.close();

    printf("Stopped processing requests.\n");

    return NO_ERROR;
}

BOOL WINAPI ControlCHandlerRoutine( _In_  DWORD dwCtrlType)
{
    g_threadsRunning = false;
    SetEvent(g_queueUpdated);
    g_localContext.close();
    return NO_ERROR;
}

int _tmain(int argc, _TCHAR* argv[])
{
    PCTSTR port = argc > 1 ? argv[1] : _T("14767");
    wstring serverAddress = _T("tcp://*:");
    serverAddress += port;
    string address = string(serverAddress.begin(), serverAddress.end());

    g_queueUpdated = CreateEvent(nullptr, FALSE, FALSE, nullptr);

    socket_t frontend(g_context, ZMQ_ROUTER);
    frontend.bind(address.c_str());
    socket_t backend(g_localContext, ZMQ_DEALER);
    backend.bind("inproc://backend");

    g_workThread = (HANDLE)_beginthreadex(nullptr, 0, (unsigned int(__stdcall *)(PVOID)) WorkThreadStatic, nullptr, 0, (unsigned *)nullptr);
    g_zmqThread = (HANDLE)_beginthreadex(nullptr, 0, (unsigned int(__stdcall *)(PVOID)) ZmqThreadStatic, nullptr, 0, (unsigned *)nullptr);

    printf("Listening on port: %ws\n", port);

    SetConsoleCtrlHandler(ControlCHandlerRoutine, TRUE);

    try
    {
        proxy(frontend, backend, nullptr);
    }
    catch (error_t& e)
    {
        if (e.num() != ERROR_WAIT_NO_CHILDREN)
        {
            printf("Proxy exception: %s\n", e.what());
        }
    }

    printf("Closing down...\n");
    CloseHandle(g_queueUpdated);

    frontend.unbind(address.c_str());

    frontend.close();
    backend.close();

    g_context.close();

    printf("Successfully shutdown.\n");

    return 0;
}

0 个答案:

没有答案