Understanding Unix/Linux Programming-用户程序:play_again3

来源:互联网 发布:sql回滚语句 编辑:程序博客网 时间:2024/04/27 22:05
  1 /* play_again3.c  2  * purpuse: ask if user wants another play   3  * better : instant response without echo  4               set tty into no-delay mode  5               read char , return result  6               reset terminal mode on Internet  7  * returns: 0 -> yes , 1 -> no   8  */  9   10  #include <stdio.h> 11  #include <stdlib.h> 12  #include <fcntl.h> 13  #include <termios.h> 14  #include <string.h> 15   16  #define ASK "Do you want another play?" 17  #define TRIES 3 18  #define SLEEPTIME 2 19  #define BEEP putchar('\a'); 20  21  22  int get_response(char *); 23  int get_ok_char(void); 24  void set_nodelay_mode(void); 25  void set_cr_noecho_mode(void); 26  void tty_mode(int);  27   28  int main() 29  { 30      int response ; 31      tty_mode(0);   // save tty mode 32      set_cr_noecho_mode(); 33      set_nodelay_mode(); 34      response = get_response(ASK); 35      tty_mode(1);   // restore tty mode 36      return response ; 37  } 38   39  int get_response(char * qiz) 40  { 41      int input ; 42      int maxtries = TRIES ; 43      printf("%s(y/n)" , qiz); 44      fflush(stdout); 45      while(1) 46      { 47         BEEP ; 48         sleep(SLEEPTIME); 49         input = tolower(get_ok_char()) ; 50         if(input == 'y') 51         { 52             printf("\n"); 53             return 0 ; 54         } 55         if(input == 'n') 56         { 57             printf("\n"); 58             return 1 ; 59         } 60         if(maxtries -- <= 0 ) 61         { 62             printf("\n"); 63             return 2 ; 64         } 65         BEEP ; 66      } 67  } 68   69 int get_ok_char(void) 70 { 71     int c ; 72     while( (c = getchar() ) != EOF && strchr("yYnN" , c ) == NULL ) 73         ; 74     return c ; 75 } 76  77 void set_cr_noecho_mode(void) 78 { 79     struct  termios ttystate ; 80     tcgetattr(0 , &ttystate); 81     ttystate.c_lflag &= ~ICANON ;   // No Buffering 82     ttystate.c_lflag &= ~ECHO ; 83     ttystate.c_cc[VMIN] = 1 ;   //Get one char one time  84     tcsetattr( 0 , TCSANOW , &ttystate);     85 } 86  87 void set_nodelay_mode(void) 88 { 89     int termflags ; 90     termflags = fcntl(0 , F_GETFL); 91     termflags |= O_NDELAY ; 92     fcntl(0 , F_SETFL , termflags) ; 93 } 94  95 void tty_mode(int mode) 96 { 97     static struct termios original_mode ;// 设置静态结构体变量 98     if(mode == 0 ) 99     {100         tcgetattr( 0 , & original_mode);// 存储原有设置101     }102     else103     {104         //还原原有设置105         if( tcsetattr(0 , TCSANOW , & original_mode) == -1 )106         {107             perror("Restore tty settings failed!\n");108         }109     }110 }

 这里使用到了非阻塞输入

  怎么解释非阻塞输入与阻塞输入?

  书上解释:

    当调用getchar或者read从文件描述符读取输入时,这些调用通常会等待输入,这叫做阻塞输入(block input)。在play_again的例子中,对于getchar的调用使得程序一直等待用户的输入,知道用户输入一个字符。程序被阻塞,知道能够获得某些字符或是检测到文件的末尾。那么如何关闭输入阻塞呢?

    阻塞不仅仅是终端连接的属性,而是任何一个打开的文件的属性。(也就是说阻塞实际上是文件的属性咯,不论是磁盘文件还是设备文件)。毕竟程序或者进程是与文件通过文件描述符连接的。

    程序可以通过fcntl或者open为文件描述符启动非阻塞输入(nonblock input)。play_again3使用fcntl为文件描述符开启O_NDELAY标志。

    关闭一个文件描述符的阻塞状态并且调用read结果会如何呢?如果能够获得输入,read会获得输入并且返回所获得的字数。如果没有输入字符,read返回0,这就像遇到文件末尾一样,如果有错误,read返回1。

    非阻塞操作内部实现非常简单。每个文件都有一块保存未读取数据的地方。如果文件描述符置位O_NDELAY,并且那块空间是空的,则read调用返回0。

    阅读O_NDELAY相关的Linux源代码,就可以了解实现细节。(准备以后再阅读源码吧

该程序的一些小问题:

  1.  运行在非阻塞模式,程序在调用getchar给用户输入字符之前睡眠2s,就算用户在1s内完成输入,程序也会在2s后得到字符
  2. 在显示提示符之后,对于fflush的调用。如果没有fflush,在调用getchar之前,提示符将不能显示。因为终端驱动程序不仅一行行地缓冲输入,而且还一行行地缓冲输出。驱动程序缓冲输出,直到它接收到一个换行符或者程序试图从终端读取输入。在这个例子中,为了给用户读提示符的时间,需要延迟读入,就必须调用fflush。
    1. 注意:fflush是将(输出)缓冲物理写入的函数。

该程序的一些大问题:

  该程序忽略一切它不想要的字母,只识别合法输入,并在规定时间间隔内无合法输入的情况下自动退出。如果输入Ctrl-C将会如何?不但会中止该程序,也会中止终端程序。为何?因为该程序运行到字符输入时,终端处于非阻塞状态,shell调用获取命令行,但是因为处于非阻塞状态,read立即返回0,程序结束时处于一个错误的状态。

  而在其他一些情况中,像bash和tcsh这些shell,当程序退出或者死亡时,它们会重置终端的属性。

0 0
原创粉丝点击