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

  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     else
103     {
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,当程序退出或者死亡时,它们会重置终端的属性。

posted on 2016-03-18 15:28  H.D  阅读(343)  评论(0编辑  收藏  举报

导航