使stdin无阻塞

时间:2011-11-11 23:11:20

标签: c

我有一个练习,我需要慢慢打印文件(间隔1秒),直到文件结束,除非用户输入一个字符。

到目前为止,程序以一秒的间隔输出文件,这很好,但是当我输入一个字符时,没有任何反应。我的猜测是我以某种方式使用选择错误。

这是我最终提交的最终计划。

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

int main(void)
{
    FILE* infile;
    char str[100];
    fd_set readset;
    struct timeval tv;

    // open a file
    if((infile = fopen("infile", "r")) == NULL)
    {
        (void)printf("Couldn't open the file\n");
        exit(1);
    }
    // file was opened successfully
    else
    {       
        // while we are not at the end of a file
        while(fgets(str, 100, infile) != NULL)
        {
            FD_ZERO(&readset);
            FD_SET(fileno(stdin), &readset);
            // set the time value to 1 second
            tv.tv_sec = 1;
            tv.tv_usec = 0;
            select(fileno(infile)+1, &readset, NULL, NULL, &tv);
            // the user typed a character so exit
            if(FD_ISSET(fileno(stdin), &readset))
            {
                fclose(infile);
                exit(0);
            }
            // the user didn't type a character so print the next line
            else
            {
                fgets(str, 100, stdin);
                puts(str);
            }
        }

        // clean up
        fclose(infile);
    }

    // report success
    return 0;
}

感谢您的帮助!

4 个答案:

答案 0 :(得分:6)

这是一个工作版本,使用tcgetattr / tcsetattr:

#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

int main(void) {
    FILE* infile;
    char str[100];
    fd_set readset;
    struct timeval tv;
    struct termios ttystate, ttysave;

    // open a file
    if((infile = fopen("infile", "r")) == NULL)
    {
        (void)printf("Couldn't open the file\n");
        exit(1);
    }
    // file was opened successfully

    //get the terminal state
    tcgetattr(STDIN_FILENO, &ttystate);
    ttysave = ttystate;
    //turn off canonical mode and echo
    ttystate.c_lflag &= ~(ICANON | ECHO);
    //minimum of number input read.
    ttystate.c_cc[VMIN] = 1;

    //set the terminal attributes.
    tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);

    // while we are not at the end of a file
    while(fgets (str, 100, infile))
    {
        // set the time value to 1 second
        tv.tv_sec = 1;
        tv.tv_usec = 0;

        FD_ZERO(&readset);
        FD_SET(fileno(stdin), &readset);

        select(fileno(stdin)+1, &readset, NULL, NULL, &tv);
        // the user typed a character so exit
        if(FD_ISSET(fileno(stdin), &readset))
        {
            fgetc (stdin); // discard character
            break;
        }
        // the user didn't type a character so print the next line
        else
        {
            puts(str);
            // not needed: sleep(1);
        }
    }

    // clean up
    fclose(infile);

    ttystate.c_lflag |= ICANON | ECHO;
    //set the terminal attributes.
    tcsetattr(STDIN_FILENO, TCSANOW, &ttysave);
    // report success
    return 0;
}

不再需要sleep(1);

答案 1 :(得分:3)

终端正在缓冲线路。在按 Enter 键之前,它不会向程序发送文本。可能有一种方法可以禁用终端线缓冲,但我认为这超出了你的任务范围。

Enter 时停止。但是,它不会立即退出。这是你想要解决的问题。摆脱sleep(1)

现在您的程序正在发送文本!你给了select超时一秒,不是吗?

// set the time value to 1 second
tv.tv_sec = 1;
tv.tv_usec = 0;

超时不坚持的原因是因为select正在修改超时值。来自the man page

  

在Linux上,select()修改超时以反映未经过的时间   睡觉;大多数其他实现不会这样做。 (POSIX.1-2001   允许任何一种行为。)这会导致Linux代码出现问题   读取超时的内容被移植到其他操作系统,以及什么时候   代码移植到Linux,重复使用struct timeval   select()s在循环中而不重新初始化它。考虑超时   select()返回后未定义。

您需要在每次调用之前初始化timeval以选择,而不只是在程序开头一次。

答案 2 :(得分:0)

你想让你的程序多线程化。创建一个每1秒间隔打印出文件的线程,主线程将从stdin获取输入,然后在获得输入时发信号通知另一个线程停止打印

答案 3 :(得分:0)

您的部分问题是您正在使用sleep(1),这将导致该行花费一秒钟来执行。如果用户键入一个字符,他们将需要等待一整秒才能在程序响应之前。因此,即使您使非阻塞部分工作,您仍然会遇到问题。

解决方案是使用nanosleepusleep暂停程序不到1秒。我的建议是使用其中一种功能睡眠1/100秒*并每次检查用户按键。在第100次,输出文件的下一部分。这样文件仍然以正确的速度运行,但是用户可以随时停止它,程序将很快响应它们的命令。