简体   繁体   English

Win32 - 从标准输入读取超时

[英]Win32 - read from stdin with timeout

I'm trying to do something which I think should be simple: do a blocking read from standard input, but timing out after a specified interval if no data is available.我正在尝试做一些我认为应该很简单的事情:从标准输入进行阻塞读取,但是如果没有可用数据,则在指定的时间间隔后超时。

In the Unix world this would be simple with select() but that doesn't work in Windows because stdin isn't a socket.在 Unix 世界中,使用select()很简单,但在 Windows 中不起作用,因为stdin不是套接字。 What's the next simplest option without creating extra threads etc?在不创建额外线程等的情况下,下一个最简单的选项是什么?

I'm using visual C++ targeting a Win32 environment.我正在使用针对 Win32 环境的视觉 C++。

so far I have tried:到目前为止,我已经尝试过:

  1. using select (doesn't work if the input is not a socket)使用select (如果输入不是套接字则不起作用)

  2. using WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE)) .使用WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE)) - Remy's first suggestion. - 雷米的第一个建议。 This always seems to return immediately when you call it if the standard input is a console (others have reported the same problem)如果标准输入是控制台,当您调用它时,这似乎总是立即返回(其他人报告了同样的问题)

  3. using overlapped IO and doing a WaitForSingleObject (Remy's third suggestion).使用重叠的 IO 并执行WaitForSingleObject (雷米的第三个建议)。 In this case the read always seems to block when the input is coming from a console - it seems that stdin does not support asynchronous I/O.在这种情况下,当输入来自控制台时,读取似乎总是阻塞 - 似乎stdin不支持异步 I/O。

At the moment I'm thinking my only remaining option is to create a thread which will do a blocking read and then signal an event, and then have another thread which waits for the event with a timeout.目前我在想我唯一剩下的选择是创建一个线程来执行阻塞读取,然后发出一个事件信号,然后让另一个线程超时等待事件。

I had to solve a similar problem. 我不得不解决类似的问题。 On Windows it is not as easy or obvious as Linux. 在Windows上,它不像Linux那么容易或明显。 It is, however, possible. 但是,这是可能的。 The trick is that Windows places console events in the console input event queue. 诀窍是Windows将控制台事件放在控制台输入事件队列中。 You've got to filter out the events you don't care about and only process those events you do care about (like key presses). 您必须过滤掉您不关心的事件,并且只处理您关心的事件(如按键)。

For further reading: see the Win32 console documentation 有关进一步阅读: 请参阅Win32控制台文档

Here is some mostly-debugged sample code based on a socket and stdin multiplexer I was working on: 以下是一些基于我正在研究的套接字和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
}

This should do it: 这应该这样做:

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

Using GetStdHandle + WaitForSingleObject works fine. 使用GetStdHandle + WaitForSingleObject工作正常。 But be sure to set the approriate flags and flush the console buffer as well before entering the loop. 但是在进入循环之前,一定要设置approriate标志并刷新控制台缓冲区。

In short (without error checks) 简而言之(没有错误检查)

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函数来获取控制台的句柄,然后您可以使用WaitForSingleObject等待事件在该句柄上发生,并超时。

Use GetStdHandle() to get the stdin handle. 使用GetStdHandle()获取stdin句柄。 You can then either: 然后你可以:

  1. use WaitForSingleObject() i the stdin handle itself to detect when there is console input available for reading, then read from it as needed. 使用WaitForSingleObject()我stdin句柄本身来检测何时有可供读取的控制台输入,然后根据需要从中读取。

  2. use GetNumberOfConsoleInputEvents() or PeekConsoleInput() on the stdin handle in a loop to determine when there is data available for reading, then read from it as needed. 在循环中的stdin句柄上使用GetNumberOfConsoleInputEvents()PeekConsoleInput()来确定何时有可用于读取的数据,然后根据需要从中读取。

  3. use ReadFile() with an OVERLAPPED structure containing an event handle, then use the event handle with WaitForSingleObject() to detect if the read times out. 使用包含事件句柄的OVERLAPPED结构的ReadFile() ,然后使用带有WaitForSingleObject()的事件句柄来检测读取是否超时。

In any case, be careful if stdin has been redirected. 在任何情况下,如果重定向stdin,请小心。 If it is redirected to something than is not console I/O, you can't use a GetStdHandle() handle with console functions. 如果将其重定向到非控制台I / O的某些内容,则不能将GetStdHandle()句柄与控制台函数一起使用。 If it is redirected to a file, you have to use ReadFile() . 如果将其重定向到文件,则必须使用ReadFile()

In case anyone is writing chrome native messaging host and is looking for solution to check if there is any input on stdin without blocking then this works perfect: 如果有人正在编写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);
    }
}

The existing answers don't address the case where the standard input is an anonymous pipe rather than a console.现有答案没有解决标准输入是匿名 pipe 而不是控制台的情况。 In this case functions like GetNumberOfConsoleInputEvents() will return 0x6 (bad handle) and the methods described above will not work.在这种情况下, GetNumberOfConsoleInputEvents()之类的函数将返回0x6 (错误句柄),并且上述方法将不起作用。

In my case I am trying to use stdin and stdout to facilitate asynchronous interprocess communication, so the parent process (nodejs) opens stdin and stdout as anonymous pipes with a child process (c++).就我而言,我正在尝试使用 stdin 和 stdout 来促进异步进程间通信,因此父进程(nodejs)将 stdin 和 stdout 作为带有子进程(c++)的匿名管道打开。 For this case, the type of stdin can be detected as follows:对于这种情况,标准输入的类型可以检测如下:

 HANDLE stdinput = GetStdHandle(STD_INPUT_HANDLE);
 if (stdinput == INVALID_HANDLE_VALUE) {
    DWORD problem1 = GetLastError();
    cout << "Failed to get input handle. " << (void*) problem1 << endl;
    return(1);
 }
 DWORD fileType = GetFileType(stdinput);
 if (fileType != FILE_TYPE_PIPE) {
    cout << "Input type is not pipe. Instead: " << (void*) fileType << endl;
    return(2);
 }

Then, as to enable asynchronous reads from this input pipe, I came up with two ideas:然后,为了从这个输入 pipe 启用异步读取,我想出了两个想法:

Method 1: Continuously poll in a loop for available input方法 1:在循环中不断轮询可用输入

do {
  DWORD bytesAvailable = 0;
  BOOL success = PeekNamedPipe(stdinput, NULL, NULL, NULL, &bytesAvailable, NULL );

  if (!success) {
    cout << "Couldn't run PeekNamedPipe." << endl;
    DWORD problem = GetLastError();
    cout << "Error code: " << (void*)problem << endl;
  }

  char buf[bytesAvailable+1]; //mingw allows dynamic stack allocation. In Visual studio might need to allocate on heap.

  if (bytesAvailable > 0) {
    ReadFile(stdinput, buf, additionalBytesAvailable, NULL, NULL);
    cout << "Received: " << buf << endl;
  }

  Sleep(10); //Small delay between checking for new input
} while(1);

Method 1 suffers from the issue that input can't be processed any faster than the small delay.方法 1 的问题是输入的处理速度不能比小延迟快。 Of course the delay could be shortened, but then the thread will consume more CPU resources.当然延迟可以缩短,但是线程会消耗更多的CPU资源。 For this reason I came up with an alternate method.出于这个原因,我想出了另一种方法。

Method 2: Block using ReadFile and send to different thread for processing.方法2:使用ReadFile阻塞,发送到不同的线程处理。 In the input processing thread, which will block when waiting for input:在输入处理线程中,等待输入时会阻塞:

do {
  char firstChar[2]; //we will read the first byte not sure if it is null terminated...

  //block until at least one byte is available
  ReadFile(stdinput, firstChar, 1, NULL, NULL);
  DWORD additionalBytesAvailable = 0;
  BOOL success = PeekNamedPipe(stdinput, NULL, NULL, NULL, &additionalBytesAvailable, NULL );
  if (!success) {
    cout << "Couldn't run PeekNamedPipe." << endl;
    DWORD problem = GetLastError();
    cout << "Error code: " << (void*)problem << endl;
  }
  char buf[additionalBytesAvailable+2]; //mingw allows stack allocation.

  buf[0] = firstChar[0];
  buf[1] = '\0';

  if (additionalBytesAvailable > 0) {    
    ReadFile(stdinput, buf+1, additionalBytesAvailable, NULL, NULL);
  }

  std::cout << count << " Read: " << buf << endl;

  //write some data to a different thread that is still responsive
  pthread_mutex_lock(&responsiveThreadLock);
  mutexProtectedString = std::string(buf);
  pthread_mutex_unlock(&responsiveThreadLock);
  PostThreadMessage(handleOfResponsiveThread, WM_NEWINPUT, NULL, NULL);
} while(1);

And in the thread that stays responsive:在保持响应的线程中:

MSG msg;
do {
  GetMessageWithTimeout(&msg, 1000);
  if (msg.message == WM_NEWINPUT) {
     std::string receivedStringCopy = "";
     pthread_mutex_lock(&responsiveThreadLock);
     receivedStringCopy  = mutexProtectedString;
     pthread_mutex_unlock(&responsiveThreadLock);
     std::cout << "Received: " << receivedStringCopy << endl;
  }
  TranslateMessage(&msg);
  DispatchMessage(&msg);
  std::cout << "Still responsive. " << endl;
} while(1);

GetMessageWithTimeout is a function designed to stay responsive (after a timeout) while waiting for a message: GetMessageWithTimeout 是一个 function 设计用于在等待消息时保持响应(超时后):

//Wait upto timeoutMs milliseconds for a message.
//Return TRUE if a message is received or FALSE if the timeout occurs or there is an error.
BOOL GetMessageWithTimeout(MSG *msg, UINT timeoutMs)
{
    //Check the message queue and return immediately if there is a message available
    BOOL hasMessage = PeekMessage(msg, NULL, 0, 0, PM_REMOVE);
    if (hasMessage) {
        return(TRUE);
    }
    else {
        //Any new messages that have arrived since we last checked the message queue will
        //cause MsgWaitForMultipleObjects to return immediately.
        //otherwise this will block the thread until a message arrives or timeout occurs
        DWORD res1 = MsgWaitForMultipleObjects(0, NULL, FALSE, timeoutMs, QS_ALLINPUT);
        if (res1 == WAIT_TIMEOUT) {
            printf("!");
            return(FALSE);
        }
        if (res1 == WAIT_OBJECT_0) {
            //If we are here, there *should* be a message available. We can just get it with PeekMessage
            hasMessage = PeekMessage(msg, NULL, 0, 0, PM_REMOVE);
            return(hasMessage);
        }
        //If we get here, its because we have a WAIT_FAILED. Don't know why this would occur, but if it
        //does, lets pause for a bit, so we don't end up in a tight loop
        Sleep(100);
        return(FALSE);
    }
}

This second method will respond immediately to new inputs.第二种方法将立即响应新的输入。 For additional robustness, it may be necessary to check and make sure that the contents of the pipe ends with a \n (so incomplete messages aren't sent) and to push messages to a vector so that multiple messages don't override one another if the receiving thread can't process them fast enough.为了获得额外的稳健性,可能需要检查并确保 pipe 的内容以\n结尾(因此不会发送不完整的消息)并将消息推送到向量以便多个消息不会相互覆盖如果接收线程不能足够快地处理它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM