Windows 事件一次由一个客户端接收

Posted

技术标签:

【中文标题】Windows 事件一次由一个客户端接收【英文标题】:Windows events are received by one client at a time 【发布时间】:2017-08-31 20:31:21 【问题描述】:

我有一个小程序 (server.exe) 每秒发送一个 Windows 事件,另一个程序 (client.exe) 等待此事件。

如果我只有一个客户端,它可以正常工作,但是当我启动多个客户端时,只有其中一个接收到下一个事件。我需要通知所有客户每个事件。我不知道如何修复我的程序。

服务器发送事件如下:

auto handle = CreateEvent(nullptr, FALSE, FALSE, "SERVER_HEARTBEAT");
SetEvent(handle)

客户端收到如下事件

static HANDLE handles[1];
handles[0] = CreateEvent(nullptr, FALSE, FALSE, "SERVER_HEARTBEAT");
auto bit_no = WaitForMultipleObjectsEx(1, 
                      static_cast<CONST HANDLE *>(handles),    
                      TRUE,                                 
                      INFINITE,                                    
                      TRUE);  

我有一个批处理文件来启动 4 个客户端和 1 个服务器:

start "cmd /c "start "client.exe" 0
start "cmd /c "start "client.exe" 1
start "cmd /c "start "client.exe" 2
start "cmd /c "start "client.exe" 3
start "cmd /c "start "server.exe"

输出显示客户端之间显然随机分布的事件:

SERVER> Sending the event each second...

CLIENT 3>  received heartbeat
CLIENT 1>  received heartbeat
CLIENT 2>  received heartbeat
  ...

server.exe 的完整代码(基于 Visual Studio 2015 构建)

#include <windows.h>
#include <iostream>

int main(int argc, char* argv[])

    std::cout << "SERVER> Sending the event each second..." << std::endl;
    std::string  line;
    auto handle = CreateEvent(nullptr, FALSE, FALSE, "SERVER_HEARTBEAT");
    if (handle == nullptr)
    
        std::cout << "error: create event failed" << std::endl;
        exit(0);
    
    while (true)
    
        if (!SetEvent(handle))
        
            std::cout << "error: SetEvent failed" << std::endl;
        
        Sleep(1000);
    
    CloseHandle(handle);
    return 0;

client.exe 的完整代码(基于 Visual Studio 2015 构建)

#include <windows.h>
#include <string>
#include <iostream>
#include <locale>

int main(int argc, char* argv[])

    // Set the client id
    if (argc != 2)
    
        std::cout << "error: missing client id" << std::endl;
        exit(0);
    

    // Validate the client id to a be positive value
    std::string input = argv[1];
    std::locale loc;
    auto it = begin(input);
    while (it != end(input) && std::isdigit(*it, loc)) ++it;
    if (it != end(input))
    
        std::cout << "invalid client id specified: " << input.c_str() << std::endl;
        return 1;
    
    auto client_id = std::stoi(input);
    if (client_id < 0)
    
        std::cout << "the client id must be a positive number: " << input.c_str() << std::endl;
        return 1;
    

    // Initialize the client to listen the heartbeat from the DCS App Manager running on the same station
    static HANDLE handles[1];
    handles[0] = CreateEvent(nullptr, FALSE, FALSE, "SERVER_HEARTBEAT");
    if (handles[0] == nullptr)
    
        std::cout << "error: create event failed" << std::endl;
        exit(0);
    
    while (true)
    
        auto bit_no = WaitForMultipleObjectsEx(
            1,                                    // number of handles in the handle array 
            static_cast<CONST HANDLE *>(handles), // pointer to the object-handle array    
            TRUE,                                 // returns when the state of any one of 
                                                  // the objects set to is signaled        
            INFINITE,                             // time-out interval never elapses       
            TRUE);                                // Alertable
        if (bit_no == WAIT_FAILED)
        
            std::cout << "error: WaitForMultipleObjects failed" << std::endl;
            break;
        
        std::cout << "CLIENT " << client_id << ">  received heartbeat" << std::endl;
    
    CloseHandle(handles[0]);
    return 0;

【问题讨论】:

您创建了一个自动重置事件。这就是自动重置事件的工作原理。 根据您的评论,我将第二个参数设置为 FALSE 以进行手动重置事件,但它没有修复它(仍然有一个客户端收到该事件)。你知道为什么吗?这里的变化: auto handle = CreateEvent(nullptr, TRUE, FALSE, "SERVER_HEARTBEAT"); 您也可以在客户端中使用 CreateEvent 而不是 OpenEvent。只有第一次调用 CreateEvent 才能实际创建事件。另一个电话 - 打开已经存在。您没有修复客户端代码来创建通知事件(手动重置)。看起来你在服务器之前运行客户端。 如果您切换到手动重置事件,您将需要使用 PulseEvent 而不是 SetEvent - 除了PulseEvent doesn't work properly。 AFAIK,唯一合理直接的解决方案是为每个客户端设置一个单独的事件对象。 (如果客户端偶尔错过心跳是可以接受的,PulseEvent 可能没问题。) 谢谢大家,你们让我理解了自动重置的工作原理以及创建与最大客户端数量一样多的事件的解决方案。如果有人想要最终解决方案,我将发布最终解决方案。再次感谢。 【参考方案1】:

下面是基于 Harry Johnston 的评论的最终解决方案,该评论告诉创建与最大客户数一样多的事件。因此,所有客户端都会定期收到服务器心跳消息。

server.exe的完整代码

#include <windows.h>
#include <iostream>
#include <string>

#define MAX_CLIENTS 20

int main(int argc, char* argv[])

    std::cout << "SERVER> Sending the event each second..." << std::endl;
    std::string  line;

    static HANDLE handles[MAX_CLIENTS];
    for (auto client_id = 0; client_id < MAX_CLIENTS; client_id++)
    
        auto eventName = std::string("SERVER_HEARTBEAT_") + std::to_string(client_id);
        std::cout << "create event " << eventName << std::endl;
        handles[client_id] = CreateEvent(nullptr, FALSE, FALSE, eventName.c_str());
        if (handles[client_id] == nullptr)
        
            std::cout << "error: create event failed" << std::endl;
            exit(0);
        
    
    while (true)
    
        std::cout << "sending events" << std::endl;
        for (auto client_id = 0; client_id < MAX_CLIENTS; client_id++)
        
            if (!SetEvent(handles[client_id]))
            
                std::cout << "error: SetEvent failed" << std::endl;
            
        
        Sleep(1000);
    

    for (auto client_id = 0; client_id < MAX_CLIENTS; client_id++)
    
        CloseHandle(handles[client_id]);
    
    return 0;

client.exe的完整代码

#include <windows.h>
#include <string>
#include <iostream>
#include <locale>

int main(int argc, char* argv[])

    // Set the client id
    if (argc != 2)
    
        std::cout << "error: missing client id" << std::endl;
        exit(0);
    

    // Validate the client id to a be positive value
    std::string input = argv[1];
    std::locale loc;
    auto it = begin(input);
    while (it != end(input) && std::isdigit(*it, loc)) ++it;
    if (it != end(input))
    
        std::cout << "invalid client id specified: " << input.c_str() << std::endl;
        return 1;
    
    auto client_id = std::stoi(input);
    if (client_id < 0)
    
        std::cout << "the client id must be a positive number: " << input.c_str() << std::endl;
        return 1;
    

    // Initialize the client to listen the heartbeat from the DCS App Manager 
    // running on the same station
    static HANDLE handles[1];
    auto eventName = std::string("SERVER_HEARTBEAT_") + std::to_string(client_id);
    std::cout << "listening for the event " << eventName << std::endl;
    handles[0] = CreateEvent(nullptr, FALSE, FALSE, eventName.c_str());
    if (handles[0] == nullptr)
    
        std::cout << "error: create event failed" << std::endl;
        exit(0);
    
    while (true)
    
        auto result = WaitForMultipleObjectsEx(
            1,                                    // number of handles in the handle array 
            static_cast<CONST HANDLE *>(handles), // pointer to the object-handle array    
            TRUE,                                 // returns when the state of any one of 
                                                  // the objects set to is signaled        
            INFINITE,                             // time-out interval never elapses       
            TRUE);                                // Alertable
        if (result == WAIT_FAILED)
        
            std::cout << "error: WaitForMultipleObjects failed" << std::endl;
            break;
        
        std::cout << "CLIENT " << client_id << ">  received heartbeat" << std::endl;
    

    CloseHandle(handles[0]);
    return 0;

【讨论】:

以上是关于Windows 事件一次由一个客户端接收的主要内容,如果未能解决你的问题,请参考以下文章

TCP和UDP的差异

管线事件

Socket.io 是不是保证按客户端顺序接收广播事件?

记录一次由屁股决定研发的狗血经历

如何在 node.js 中接收 socket.io 客户端事件?

记录一次由事务可重复读引起的问题