16

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

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

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

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

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

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

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

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

4

6 回答 6

6

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

如需进一步阅读:请参阅 Win32 控制台文档

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

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
}
于 2014-03-10T20:30:32.560 回答
4

使用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);
于 2016-02-19T10:03:29.653 回答
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;
}
于 2014-02-13T08:43:05.673 回答
2

如果有人正在编写 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);
    }
}
于 2016-01-28T11:48:40.310 回答
1

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

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

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

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

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

于 2013-11-13T20:53:36.207 回答
0

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

于 2013-11-13T14:04:21.823 回答