Win32 - чтение с stdin с таймаутом
Я пытаюсь сделать что-то, что, по моему мнению, должно быть простым: сделать блокировку прочитанной со стандартного ввода, но тайм-аут после определенного интервала, если нет данных.
В мире Unix это было бы просто с select(), но это не работает в Windows, потому что stdin не является сокетом. Какая следующая простейшая опция без создания дополнительных потоков и т.д.
Я использую визуальный С++, предназначенный для среды Win32.
до сих пор я пробовал:
-
с помощью select (не работает, если вход не является сокетом)
-
с помощью WaitForSingleObject (GetStdHandle (STD_INPUT_HANDLE)). - Первое предложение Рэми. Кажется, что это всегда возвращается сразу, когда вы вызываете его, если стандартный ввод является консолью (другие сообщили о той же проблеме)
-
с использованием перекрытого ввода-вывода и выполнения WaitForSingleObject (remy third suggestion). В этом случае чтение всегда кажется заблокированным, когда вход поступает с консоли - кажется, что stdin не поддерживает асинхронный ввод-вывод.
В настоящий момент я думаю, что моя единственная оставшаяся опция - создать поток, который будет читать блокировку, а затем сигнализировать о событии, а затем еще один поток, ожидающий события с таймаутом.
Ответы
Ответ 1
Мне пришлось решить подобную проблему. В 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
}
Ответ 2
Это должно сделать это:
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 unexpected was returned.";
}
}
return 0;
}
Ответ 3
Поздний ответ, но, возможно, полезная информация.
Использование 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);
Ответ 4
Вам понадобится функция GetStdHandle, чтобы получить дескриптор консоли, затем вы можете использовать WaitForSingleObject, чтобы дождаться события, произошедшего на этом дескрипторе, с таймаутом.
Ответ 5
Используйте GetStdHandle()
, чтобы получить дескриптор stdin. Затем вы можете:
-
используйте WaitForSingleObject()
я сам дескриптор stdin, чтобы определить, когда есть доступ к консоли для чтения, затем прочитайте его при необходимости.
-
используйте GetNumberOfConsoleInputEvents()
или PeekConsoleInput()
в дескрипторе stdin в цикле, чтобы определить, когда имеются данные для чтения, затем прочитайте их при необходимости.
-
используйте ReadFile()
с структурой OVERLAPPED
, содержащей дескриптор события, затем используйте дескриптор события с WaitForSingleObject()
, чтобы определить, не истечет время чтения.
В любом случае, будьте осторожны, если stdin был перенаправлен. Если он перенаправлен на что-то, кроме консольного ввода-вывода, вы не можете использовать дескриптор GetStdHandle()
с функциями консоли. Если он перенаправлен в файл, вы должны использовать ReadFile()
.
Ответ 6
Если кто-то пишет chrome native messaging host и ищет решение для проверки наличия каких-либо данных на 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);
}
}