【问题标题】:Win32 - read from stdin with timeoutWin32 - 从标准输入读取超时
【发布时间】:2013-11-26 03:54:15
【问题描述】:

我正在尝试做一些我认为应该很简单的事情:从标准输入进行阻塞读取,但如果没有数据可用,则会在指定的时间间隔后超时。

在 Unix 世界中,使用 select() 会很简单,但在 Windows 中却不行,因为 stdin 不是套接字。在不创建额外线程等的情况下,下一个最简单的选项是什么?

我正在使用针对 Win32 环境的 Visual C++。

到目前为止我已经尝试过:

  1. 使用select(如果输入不是套接字则不起作用)

  2. 使用WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE))。 - 雷米的第一个建议。如果标准输入是控制台,这似乎总是在您调用它时立即返回(其他人报告了同样的问题)

  3. 使用重叠 IO 并执行 WaitForSingleObject(Remy 的第三个建议)。在这种情况下,当输入来自控制台时,读取似乎总是阻塞 - 似乎stdin 不支持异步 I/O。

目前我在想我唯一剩下的选择是创建一个线程来执行阻塞读取,然后发出一个事件信号,然后让另一个线程超时等待事件。

【问题讨论】:

  • 您很好地解释了您的问题,但您对此做了什么尝试?
  • 如果您正确地从 STD_INPUT_HANDLE 中删除初始事件,则使用 WaitForSingleObject() 进行的 #2 尝试会奏效。就我而言,我从一开始就在控制台队列中有一个“焦点”事件。看看@Clay 的回答,没错。

标签: c++ c winapi stdio


【解决方案1】:

我不得不解决一个类似的问题。在 Windows 上,它不像 Linux 那样简单或明显。然而,这是可能的。诀窍是 Windows 将控制台事件放在控制台输入事件队列中。你必须过滤掉你不关心的事件,只处理那些你关心的事件(比如按键)。

进一步阅读:see the Win32 console documentation

这是一些基于我正在研究的套接字和标准输入多路复用器的大部分调试示例代码:

void ProcessStdin(void)
{
    INPUT_RECORD record;
    DWORD numRead;
    if(!ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &record, 1, &numRead)) {
        // hmm handle this error somehow...
        return;
    }

    if(record.EventType != KEY_EVENT) {
        // don't care about other console events
        return;
    }

    if(!record.Event.KeyEvent.bKeyDown) {
        // really only care about keydown
        return;
    }

    // if you're setup for ASCII, process this:
    //record.Event.KeyEvent.uChar.AsciiChar

} // end ProcessStdin

int main(char argc, char* argv[])
{
    HANDLE eventHandles[] = {
        GetStdHandle(STD_INPUT_HANDLE)
        // ... add more handles and/or sockets here
        };

    DWORD result = WSAWaitForMultipleEvents(sizeof(eventHandles)/sizeof(eventHandle[0]), 
        &eventHandles[0], 
        FALSE, 
        1000, 
        TRUE
        );

    switch(result) {
        case WSA_WAIT_TIMEOUT: // no I/O going on right now
            break;

        case WSA_WAIT_EVENT_0 + 0: // stdin at array index 0
            ProcessStdin();
            break;

        case WSA_WAIT_EVENT_0 + 1: // handle/socket at array index 1
            break;

        case WSA_WAIT_EVENT_0 + 2: // ... and so on
            break;

        default: // handle the other possible conditions
            break;
    } // end switch result
}

【讨论】:

    【解决方案2】:

    使用GetStdHandle + WaitForSingleObject 可以正常工作。但请务必在进入循环之前设置适当的标志并刷新控制台缓冲区。

    简而言之(没有错误检查)

    std::string inStr;
    DWORD fdwMode, fdwOldMode;
    HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
    GetConsoleMode(hStdIn, &fdwOldMode);
    // disable mouse and window input
    fdwMode = fdwOldMode ^ ENABLE_MOUSE_INPUT ^ ENABLE_WINDOW_INPUT;
    SetConsoleMode(hStdIn, fdwMode);
    // flush to remove existing events
    FlushConsoleInputBuffer(hStdIn);
    while (!abort)
    {
        if (WaitForSingleObject(hStdIn, 100) == WAIT_OBJECT_0)
        {
             std::getline(std::cin, inStr);
        }
    }
    // restore console mode when exit
    SetConsoleMode(hStdIn, fdwOldMode);
    

    【讨论】:

    • 使用ReadConsoleInput 代替getline 并避免禁用其他事件。
    • 这会切换ENABLE_MOUSE_INPUTENABLE_WINDOW_INPUT 而不是禁用它们,这是一个错误。而且,这还不够。 WaitForSingleObject 仍将检测焦点更改和菜单事件,如果缓冲区中没有任何内容,这将导致 ReadConsolegetline 阻塞。
    【解决方案3】:

    应该这样做:

    int main()
    {
        static HANDLE stdinHandle;
        // Get the IO handles
        // getc(stdin);
        stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
    
        while( 1 )
        {
            switch( WaitForSingleObject( stdinHandle, 1000 ) )
            {
            case( WAIT_TIMEOUT ):
                cerr << "timeout" << endl;
                break; // return from this function to allow thread to terminate
            case( WAIT_OBJECT_0 ):
                if( _kbhit() ) // _kbhit() always returns immediately
                {
                    int i = _getch();
                    cerr << "key: " << i << endl;
                }
                else // some sort of other events , we need to clear it from the queue
                {
                    // clear events
                    INPUT_RECORD r[512];
                    DWORD read;
                    ReadConsoleInput( stdinHandle, r, 512, &read );
                    cerr << "mouse event" << endl;
                }
                break;
            case( WAIT_FAILED ):
                cerr << "WAIT_FAILED" << endl;
                break;
            case( WAIT_ABANDONED ): 
                cerr << "WAIT_ABANDONED" << endl;
                break;
            default:
                cerr << "Someting's unexpected was returned.";
            }
        }
    
        return 0;
    }
    

    【讨论】:

    • 这个有问题。如果事件(例如鼠标移动)继续到达,则 WaitForSingleObject() 永远不会超时,因此您永远不会得到案例(WAIT_TIMEOUT)。您需要另一种机制来检查每次循环所用的时间。
    • stdinHandle 可以被重定向。在调用适当的 API 函数之前,您必须检查句柄的类型:GetFileTypeFILE_TYPE_DISK -> ReadFile; FILE_TYPE_CHAR -> ReadConsoleInputPeekConsoleInput; FILE_TYPE_PIPE -> ReadFilePeekNamedPipe。否则ReadConsoleInput 将失败。
    【解决方案4】:

    如果有人正在编写 chrome 原生消息传递主机并正在寻找解决方案来检查 stdin 上是否有任何输入而不会阻塞,那么这很完美:

    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
    int timer = GetTickCount();
    while(timer+10000 > GetTickCount())
    {
        unsigned int length = 0;
        DWORD bytesAvailable = 0; 
        PeekNamedPipe(hStdin,NULL,0,NULL,&bytesAvailable,NULL);
        if(bytesAvailable > 0)
        {
            for (int i = 0; i < 4; i++)
            {
                unsigned int read_char = getchar();
                length = length | (read_char << i*8);
            }
    
    
            for (int i = 0; i < length; i++)
            {
                msg += getchar();
            }
            timer = GetTickCount();
        }
        else
        {
            // nothing to read, stdin empty
            Sleep(10);
        }
    }
    

    【讨论】:

    • 太棒了——在阅读之前尝试了许多不同的方法来检查可用数据之后,这是唯一有效的方法。
    【解决方案5】:

    使用GetStdHandle() 获取标准输入句柄。然后您可以:

    1. 使用WaitForSingleObject() i stdin 句柄本身来检测何时有控制台输入可供读取,然后根据需要从中读取。

    2. 在循环中对标准输入句柄使用GetNumberOfConsoleInputEvents()PeekConsoleInput() 以确定何时有数据可供读取,然后根据需要从中读取。

    3. ReadFile() 与包含事件句柄的OVERLAPPED 结构一起使用,然后将事件句柄与WaitForSingleObject() 一起使用来检测读取是否超时。

    在任何情况下,如果 stdin 已被重定向,请小心。如果它被重定向到不是控制台 I/O 的东西,则不能将 GetStdHandle() 句柄与控制台功能一起使用。如果重定向到文件,则必须使用ReadFile()

    【讨论】:

    • 感谢 Remy - 这一直在变得越来越复杂 :( 实际上我正在编写一个 google chrome 原生消息传递主机。在现实世界中,它将由具有标准输入和输出的 chrome 进程调用与此相关, 但我假设只使用通用输入/输出函数会很简单, 以便我可以从命令行对其进行测试。似乎在 Windows 控制台应用程序的世界中, 这些事情不像 *尼克斯!看起来选项 3 可能最有可能奏效
    【解决方案6】:

    您需要GetStdHandle 函数来获取控制台句柄,然后您可以使用WaitForSingleObject 等待该句柄上发生的事件,并超时。

    【讨论】:

    • 天啊!谢谢。我对异步 I/O 和 SleepEx 很感兴趣。这看起来就像我一直在寻找的东西:-)
    • 嗯,经过更多的尝试,这似乎不起作用。最初调用 stdin 上的 WaitForSingleObject 似乎总是返回 WAIT_OBJECT_0,即使从 stdin 读取会阻塞。
    • 根据文档:“进程可以在其中一个等待函数中指定控制台输入缓冲区句柄,以确定何时有未读的控制台输入。当输入缓冲区不为空时,a 的状态控制台输入缓冲区句柄已发出信号。”也许控制台输入上有一些内容,但您尝试阅读的内容超出了可用范围?
    • 其他人似乎也遇到过同样的问题——我认为可能有其他事件(例如鼠标事件)不是标准输入,但仍会导致对象出现信号。正如您将在下面看到的那样,我的应用程序不会在现实世界中从控制台调用(这只是为了测试)所以我试图避免这种情况并改用一些更通用的机制。
    猜你喜欢
    • 1970-01-01
    • 2013-03-30
    • 2012-02-17
    • 1970-01-01
    • 2015-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多