Linux C语言实现按键即时识别

top中的源代码,慢慢理解。。。
不需要按ENTER键,按下即可识别

一、代码片段

部分摘自top源代码,部分来源于网络博客

text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <sys/time.h>
#include <stdio.h>
#include <termios.h>

/* The original and new terminal attributes */
static struct termios Savedtty,
                     Rawtty;
// 设置终端相关属性
static void initKeyboard()
{
   tcgetattr(0,&Savedtty);
   Rawtty = Savedtty;
   Rawtty.c_lflag &= ~ICANON;    // 设置不以规范模式工作,读请求直接从队列读取字符,至少接到MIN字节或者两个字节之间超时值TIME到期时,read才返回
   Rawtty.c_lflag &= ~ECHO;      // 关闭输入字符回显到终端设备
   // Rawtty.c_lflag &= ~ISIG;   // 判断输入字符是否要产生终端信号的特殊字符
   Rawtty.c_cc[VMIN] = 1;        // 至少接到MIN字节
   Rawtty.c_cc[VTIME] = 0;       // 两个字节之间超时值TIME
   tcsetattr(0, TCSANOW, &Rawtty);
}
// 恢复终端属性
static void closeKeyboard()
{
   tcsetattr(0, TCSANOW, &Savedtty);
}
// 读取字符
static int chin (int ech, char *buf, unsigned cnt)
{
  int rc;
  fflush(stdout);
  if (!ech)
     rc = read(STDIN_FILENO, buf, cnt);
  else {
     tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty);
     rc = read(STDIN_FILENO, buf, cnt);
     tcsetattr(STDIN_FILENO, TCSAFLUSH, &Rawtty);
  }
  // may be the beginning of a lengthy escape sequence
  tcflush(STDIN_FILENO, TCIFLUSH);
  return rc;                   // note: we do NOT produce a vaid 'string'
}

// 判断读取字符的有效性
static char kbhit(){
   long file_flags;
   int rc;
   char c;
   fd_set fs;
   FD_ZERO(&fs);
   FD_SET(STDIN_FILENO, &fs);
   file_flags = fcntl(STDIN_FILENO, F_GETFL);
   if(file_flags==-1) file_flags=0;

   fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK|file_flags);

   // check 1st, in case tv zeroed (by sig handler) before it got set
   rc = chin(0, &c, 1);
   if (rc <= 0) {
       // EOF is pretty much a "can't happen" except for a kernel bug.
       // We should quickly die via SIGHUP, and thus not spin here.
       // if (rc == 0) end_pgm(0); /* EOF from terminal */
       fcntl(STDIN_FILENO, F_SETFL, file_flags);
       select(1, &fs, NULL, NULL, NULL);
       fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK|file_flags);
  }

   if (chin(0, &c, 1) > 0) {
       fcntl(STDIN_FILENO, F_SETFL, file_flags);
       return c;
  } else {
       fcntl(STDIN_FILENO, F_SETFL, file_flags);
  }
   kbhit();
}

## use
initKeyboard();
while(true){
  char input = kbhit();
  if( input == 'q'){
      // do something
      break;
  }
}
closeKeyboard();

二、相关知识

2.1 终端相关

text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <termios.h>
// 终端属性结构体
struct termios{
   tcflag_t c_iflag; // 输入标志
   tcflag_t c_oflag; // 输出标志
   tcflag_t c_cflag; // 控制标志
   tcflag_t c_lflag; // 本地标志
   cc_t     c_cc[NCCS]; // 控制字符
};

// 获取终端的属性
tcgetattr(int fd, struct termios* tty_struct);

// 设置终端属性
tcsetattr(int fd, int opt, const struct termios* tty_struct);

tcsetattr的opt参数指定新设置的终端属性什么时候起作用

  • TCSANOW:更改立即发生
  • TCSADRAIN:发送了所有的输出后更改才发生
  • TCSAFLUSH:发送了所有的输出后更改才发生。更进一步,更改发生时未读入的所有输入都会被丢弃

标志详细见APUE

2.2 select函数

允许进程指示内核等待多个事件中的任何一个发生,并且只有一个或者多个事件或者超时之后才唤醒它

text
1
2
3
4
5
#include <sys/select.h>
#include <sys/time.h>

int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
// return:返回就绪的描述符的数目,超时返回0,出错返回-1
  • 若timeout为空指针,则永远等下去
  • 若timeout中秒数和微妙数设置为0,检查描述符之后立即返回
  • 若timeout中秒数和微妙数设置不为0,在有描述符就绪时返回,但是不超过timeout指定的时间

2.3 fcntl函数

text
1
2
3
4
5
6
int fcntl(int fd, int cmd);

int fcntl(int fd, int cmd, long arg);

int fcntl(int fd, int cmd, struct flock *lock);
//fcntl()针对(文件)描述符提供控制.参数fd是被参数cmd操作(如下面的描述)的描述符。针对cmd的值,fcntl能够接受第三个参数(arg)

fcntl函数功能:

  • 复制一个现有的描述符(cmd=F_DUPFD)
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
  • .获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

三、参考文献

【1】top相关源码