Win32 – 从超时读取stdin
我正在尝试做一些我认为应该简单的事情:从标准输入执行阻塞读取,但如果没有可用数据,则在指定的时间间隔后超时。
在Unix世界中,这对于select()
来说很简单,但是在Windows中不起作用,因为stdin
不是套接字。 如果没有创建额外的线程等,下一个最简单的选择是什么?
我正在使用针对Win32环境的visual C ++。
到目前为止我尝试过:
-
使用
select
(如果输入不是套接字则不起作用) -
使用
WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE))
。 – 雷米的第一个建议。 如果标准输入是控制台(其他人报告了同样的问题),当你调用它时,它总是会立即返回 -
使用重叠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句柄。 然后你可以:
-
使用
WaitForSingleObject()
我的stdin句柄本身来检测何时有可供读取的控制台输入,然后根据需要从中读取。 -
在循环中的stdin句柄上使用
GetNumberOfConsoleInputEvents()
或PeekConsoleInput()
来确定何时有可用于读取的数据,然后根据需要从中读取。 -
使用包含事件句柄的
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
等待事件在该句柄上发生,并超时。