18

I want to fulfill the following things in a console application:

  1. If user inputs a character, the application will do the corresponding task. For example, if user inputs 1, the program will do task 1, if user inputs q, the program will quit;
  2. If user inputs nothing, the program will do the default task every 10 seconds (the time needn't to be very strict).

Here is my code:

#include <stdio.h>
#include <time.h>

char buff[64];
char command;

while(command != 'q')
{
   begin:
   printf(">> ");
   scanf("%s", buff);
   command = buff[0];

   switch (command)
   {
       case '1':
       // task 1 code will be added here;
          break;
       case '2':
       // task 2 code will be added here;
          break;
       case 'q':
          printf("quit the loop.\n");
          break;
   }

   // wait for 10 seconds;
   Sleep(10000);
   // default task code will be added here;

  if(command != 'q')
  {
     goto begin;
  }

}

But the problem is the program will trap at the line of scanf() function forever to wait for an input character, if no character is entered. So I'm wondering how to skip the line of scanf() after a certain time, I mean for example, if no input after 1 second, the program can continue, so as to fulfill the second thing listed above.

The platform is Windows, if it matters.

I've removed the semicolon after the while() it was an obvious mistake.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
user3208536
  • 191
  • 1
  • 1
  • 4
  • 2
    I don't think there's a standard way to achieve this - i.e. a timeout. Which platform are you targeting ? – cnicutar Jan 17 '14 at 23:42
  • 1
    what platform are you on. If you are on linux you might `poll` or `select` to see if something occurs on STDIN_FILENO – hetepeperfan Jan 17 '14 at 23:42
  • goto begin? you are testing it in the while condition (which ends at the `;`, thus remove this), so you don't need it (rather, a "continue"), nor the `if`. — to do more "interactive" input, you can take a look at ncurses library; there's some literature on SO you could be interested in, e.g. [here](http://stackoverflow.com/questions/7772341/how-to-get-a-character-from-stdin-without-waiting-for-user-to-put-it);maybe you can arrange something with timeout — but of course your code would depend on that library which is not part of the std lib. – ShinTakezou Jan 17 '14 at 23:55
  • possible duplicate of [Timeout implementation in C for TFTP](http://stackoverflow.com/questions/16501741/timeout-implementation-in-c-for-tftp) – Lee Duhem Jan 18 '14 at 00:50
  • you haven't initialize `command`so the first loop is undefined. Also, remove `;` at the end of the while loop or your program won't loop at all – phuclv Jan 18 '14 at 02:35

6 Answers6

13

Try using the select() function. Then you can wait for 10 seconds until you can read from stdin without blocking. If select() returns with zero, perform the default action. I don't know if this works on windows, it's POSIX standard. If you happen to develop on unix/linux, try man select

I just wrote a working example using select:

#include <stdlib.h>
#include <stdio.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define MAXBYTES 80

int main(int argc, char *argv[])
{
        fd_set readfds;
        int    num_readable;
        struct timeval tv;
        int    num_bytes;
        char   buf[MAXBYTES];
        int    fd_stdin;

        fd_stdin = fileno(stdin);

        while(1) {
                FD_ZERO(&readfds);
                FD_SET(fileno(stdin), &readfds);

                tv.tv_sec = 10;
                tv.tv_usec = 0;

                printf("Enter command: ");
                fflush(stdout);
                num_readable = select(fd_stdin + 1, &readfds, NULL, NULL, &tv);
                if (num_readable == -1) {
                        fprintf(stderr, "\nError in select : %s\n", strerror(errno));
                        exit(1);
                }
                if (num_readable == 0) {
                        printf("\nPerforming default action after 10 seconds\n");
                        break;  /* since I don't want to test forever */
                } else {
                        num_bytes = read(fd_stdin, buf, MAXBYTES);
                        if (num_bytes < 0) {
                                fprintf(stderr, "\nError on read : %s\n", strerror(errno));
                                exit(1);
                        }
                        /* process command, maybe by sscanf */
                        printf("\nRead %d bytes\n", num_bytes);
                        break; /* to terminate loop, since I don't process anything */
                }
        }

        return 0;
}

Note: the poll() example below is OK too, no problem. For the rest I chose to read the available bytes into a buffer (up to MAXBYTES). It can be (s)scanned afterwards. (scanf() just isn't too much my friend, but that's a personal taste matter).

Ronald
  • 2,842
  • 16
  • 16
  • Here is a similar question with an example - http://stackoverflow.com/questions/7226603/timeout-function. – Graeme Jan 17 '14 at 23:49
  • Thank you so much Ronald and Graeme. However, I tried the example that Graeme suggested, but the command window will display "Unable to read your input" forever, and no delay between the two displays. So I'm wondering if other things should be paid attention for the configuration of the `select()` function and its initialization, such as `FD_ZERO` and `FD_SET`. thank you very much. – user3208536 Jan 20 '14 at 21:31
  • Thank you Ronald, thank you so much for your code. I just tried your code but the value of num_readable is always -1, I cannot use keyboard to input anything. also I cannot find the head file `unistd.h`, so I cannot use the `read()` function. I'm using Microsoft Visual C++ 2010 Express in Windows 7. – user3208536 Jan 21 '14 at 02:36
  • OK, I missed your note about using windows. I'm not so much a windows specialist, but read() doesn't do something special. Basically you need a function that reads from stdin without blocking if it can't read as many characters as fit in its buffer. I think it's called ReadFile() in Windows. Instead of strerror() you might want to use FormatMessage(). – Ronald Jan 21 '14 at 06:40
  • Hi Ronald, sorry to bother you again. The problem is that after the _"Enter Command:"_ I cannot input any characters, and the return value of the select() function is always -1, the screen will display: _"Error in select : No error"_. Do you have any ideas about this problem? – user3208536 Jan 22 '14 at 20:46
  • np. A bit of googling revealed that the select() call under Windows targets at sockets. Any other form of async I/O uses the ReadFile() function, in conjunction with the npOverlapped parameter. That would mean you'll have to use the Windows I/O interface. Not everything is lost yet. Maybe you're lucky. The parameter readfds will have bits set for all files that are readable, which can be tested by `if (FD_ISSET(fd_stdin, readfds)) ...` instead of the returnvalue. This might or might not work, but it's worth a try. – Ronald Jan 23 '14 at 07:08
9

Here is a working example of how to do this with poll (probably the most 'correct' way on Linux):

#include <unistd.h>
#include <poll.h>
#include <stdio.h>

int main()
{
    struct pollfd mypoll = { STDIN_FILENO, POLLIN|POLLPRI };
    char string[10];

    if( poll(&mypoll, 1, 2000) )
    {
        scanf("%9s", string);
        printf("Read string - %s\n", string);
    }
    else
    {
        puts("Read nothing");
    }

    return 0;
}

The timeout is the third argument to poll and is in milliseconds - this example will wait for 2 seconds for input on stdin. Windows has WSAPoll, which should work similarly.

Graeme
  • 2,971
  • 21
  • 26
3

But the problem is the program will trap at the line of scanf() function forever to wait for an input character,

Remove the semicolon after while.

haccks
  • 104,019
  • 25
  • 176
  • 264
  • 2
    Your absolutly right about the semicolon. However, how does this solve the problem that if the user doesn't input anything the program will do the default task after 10 seconds? – hetepeperfan Jan 17 '14 at 23:47
  • 1
    It is just about the first question. – haccks Jan 17 '14 at 23:48
2

Try alarm(3)

#include <stdio.h>
#include <unistd.h>
int main(void)
{
   char buf [10];
   alarm(3);
   scanf("%s", buf);
   return 0;
}
lbaby
  • 2,488
  • 2
  • 16
  • 12
2

As others have said, the best way to do truly async IO is with select(...).

But a quick and dirty way to do what you want is with getline(...) which will return the number of bytes read every time (not hanging on IO) and returns -1 on no bytes read.

The following is from the getline(3) man page:

// The following code fragment reads lines from a file and writes them to standard output.  
// The fwrite() function is used in case the line contains embedded NUL characters.

char *line = NULL;
size_t linecap = 0;
ssize_t linelen;
while ((linelen = getline(&line, &linecap, fp)) > 0)
    fwrite(line, linelen, 1, stdout);
mellowfish
  • 664
  • 6
  • 14
  • I'm wondering which library is the function `getline(...)` in, and what's the meaning of the third parameter `fp`, thanks. – user3208536 Jan 20 '14 at 21:21
  • Again, this is straight from `man getline`: "Standard C Library (libc, -lc)". `#include ` `fp` is the file pointer you are reading from, like standard in or something. – mellowfish Jan 22 '14 at 19:58
2

Unfortunately, what you are asking for is not possible in plain ISO C. However, most platforms offer platform-specific extensions which provide the functionality that you require.

Since you stated that your question applies to Microsoft Windows, the two functions that you are looking for are WaitForSingleObject and ReadConsoleInput. You won't be able to use the function scanf or C-style streams (FILE*) for this.

The function WaitForSingleObject allows you to wait until a specific object is in a signalled state. It also allows you to set a timeout (which should be 10 seconds in your case).

One type of object that WaitForSingleObject can wait for is a console handle. It will be in a signalled state when there is new input available to be read by ReadConsoleInput. According to my tests, it won't work reliably with ReadConsole though, because even if WaitForSingleObject indicates that input is waiting, the function ReadConsole will still sometimes block. This is because in contrast to ReadConsoleInput, the function ReadConsole will filter some events. Therefore, attempting to read one event with ReadConsole may actually attempt to read more than one raw event, which will cause the function to block if there are no non-filtered raw events available. The function ReadConsoleInput does not have this problem, as it works with raw events directly and does not filter any.

Here is my program which uses the functions mentioned above, and does exactly what you asked for.

#define WIN32_LEAN_AND_MEAN

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

void DoDefaultTask()
{
    printf( "Doing default task.\n" );
}

void DoTask1()
{
    printf( "Doing task #1.\n" );
}

void DoTask2()
{
    printf( "Doing task #2.\n" );
}

int main(void)
{
    INPUT_RECORD ir;
    HANDLE hConInput = GetStdHandle(STD_INPUT_HANDLE);
    DWORD dwReadCount;
    bool should_prompt = true, quit = false;

    while ( !quit )
    {
        //prompt user for input
        if ( should_prompt )
        {
            printf( "Please select an option: " );
            should_prompt = false;
        }

        //flush output
        fflush( stdout );

        switch ( WaitForSingleObject( hConInput, 10000 ) )
        {
        case WAIT_OBJECT_0:

            //attempt to read input
            if ( !ReadConsoleInput( hConInput, &ir, 1, &dwReadCount ) || dwReadCount != 1 )
            {
                fprintf( stderr, "Unexpected input error!\n" );
                exit( EXIT_FAILURE );
            }

            //only handle key-down events
            if ( ir.EventType != KEY_EVENT || !ir.Event.KeyEvent.bKeyDown )
                break;

            //echo output, if character is printable
            if ( isprint( (unsigned char)ir.Event.KeyEvent.uChar.AsciiChar ) )
            {
                printf( "%c", ir.Event.KeyEvent.uChar.AsciiChar );
            }

            printf( "\n" );

            switch ( ir.Event.KeyEvent.uChar.AsciiChar )
            {
            case '1':
                DoTask1();
                break;
            case '2':
                DoTask2();
                break;
            case 'q':
                printf( "Quitting program...\n" );
                quit = true;
                break;
            default:
                printf( "Unknown command.\n" );
            }

            should_prompt = true;

            break;

        case WAIT_TIMEOUT:
            printf( "Timeout!\n" );
            DoDefaultTask();
            should_prompt = true;
            break;

        default:
            fprintf( stderr, "unexpected error!" );
            exit( EXIT_FAILURE );
        }
    }

    return 0;
}

This program has the following behavior:

Please select an option: 1
Doing task #1.
Please select an option: 2
Doing task #2.
Please select an option: 3
Unknown command.
Please select an option: 4
Unknown command.
Please select an option: Timeout!
Doing default task.
Please select an option: Timeout!
Doing default task.
Please select an option: 1
Doing task #1.
Please select an option: 3
Unknown command.
Please select an option: 2
Doing task #2.
Please select an option: 1
Doing task #1.
Please select an option: Timeout!
Doing default task.
Please select an option: q
Quitting program...
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39