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源代码,就可以了解实现细节。(准备以后再阅读源码吧)
该程序的一些小问题:
- 运行在非阻塞模式,程序在调用getchar给用户输入字符之前睡眠2s,就算用户在1s内完成输入,程序也会在2s后得到字符
- 在显示提示符之后,对于fflush的调用。如果没有fflush,在调用getchar之前,提示符将不能显示。因为终端驱动程序不仅一行行地缓冲输入,而且还一行行地缓冲输出。驱动程序缓冲输出,直到它接收到一个换行符或者程序试图从终端读取输入。在这个例子中,为了给用户读提示符的时间,需要延迟读入,就必须调用fflush。
- 注意:fflush是将(输出)缓冲物理写入的函数。
该程序的一些大问题:
该程序忽略一切它不想要的字母,只识别合法输入,并在规定时间间隔内无合法输入的情况下自动退出。如果输入Ctrl-C将会如何?不但会中止该程序,也会中止终端程序。为何?因为该程序运行到字符输入时,终端处于非阻塞状态,shell调用获取命令行,但是因为处于非阻塞状态,read立即返回0,程序结束时处于一个错误的状态。
而在其他一些情况中,像bash和tcsh这些shell,当程序退出或者死亡时,它们会重置终端的属性。