Win32 – 从超时读取stdin

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

在Unix世界中,这对于select()来说很简单,但是在Windows中不起作用,因为stdin不是套接字。 如果没有创建额外的线程等,下一个最简单的选择是什么?

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

到目前为止我尝试过:

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

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

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

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

我不得不解决类似的问题。 在Windows上,它不像Linux那么容易或明显。 但是,这是可能的。 诀窍是Windows将控制台事件放在控制台输入事件队列中。 您必须过滤掉您不关心的事件,并且只处理您关心的事件(如按键)。

有关进一步阅读: 请参阅Win32控制台文档

这是一些基于我正在研究的套接字和stdin多路复用器的大多数调试样本代码:

 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 } 

这应该这样做:

 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; } 

使用GetStdHandle + WaitForSingleObject工作正常。 但是在进入循环之前,一定要设置approriate标志并刷新控制台缓冲区。

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

 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); 

使用GetStdHandle()获取stdin句柄。 然后你可以:

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

  2. 在循环中的stdin句柄上使用GetNumberOfConsoleInputEvents()PeekConsoleInput()来确定何时有可用于读取的数据,然后根据需要从中读取。

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

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

如果有人正在编写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); } } 

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