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