如何在不阻塞的情况下捕获 Unix 控制台应用程序中的单个击键?
题
我有一个用 C 编写的非常简单的 TCP 服务器。它无限期地运行,等待连接。在 Windows 上,我使用 select
检查套接字上的活动,如果没有活动,我有以下代码允许我通过按键盘上的“q”退出:
if( kbhit() ) {
char c = getch();
if( c == 'q' ) break;
}
这在 unix 上不起作用,因为 kbhit
不存在并且 getch
工作方式不同。我找到了一些 示例代码 使用 tcsetattr
更改终端设置并允许逐个字符输入。调用 init 函数后,我打开 /dev/stdin (使用 O_NONBLOCK
)并读取一个字符,但是 read( f, &c, 1 )
阻止直到击中角色。
我想我可以生成一个单独的线程并有 它 无限期地等待,然后在用户点击“q”时向第一个线程发出信号,但这似乎有点严厉。当然有更简单的方法吗?
解决方案
将 stdin 添加到选择句柄列表中,如果它有数据,则调用 read 从中读取一个字符。
其他提示
相反,添加“f”
read( f, &c, 1 )
选择通话。当 f 准备好读取时,一个字符已被按下,read() 不会阻塞。
在 Unix 中,无论是在系统控制台还是在 X 终端窗口中,键盘 I/O 都通过虚拟终端。如今,设备 /dev/tty 是访问进程控制终端的常用方法。除打开/关闭/读/写之外的设备操作均由该特定设备的 ioctl(2) 系统调用处理。你想做的事情的总体思路是
打开控制终端(可能是也可能不是标准输入)
更改该终端上的操作模式以返回,而无需等待整行输入(这是正常的默认值)
继续程序的其余部分,知道从该终端(可能是标准输入)读取可能会返回部分行甚至零字符,而不会出现错误或终止条件。
有关如何执行第二步的详细答案可以在 C 编程常见问题解答. 。他们还指出这是一个操作系统问题,而不是语言问题。它们提供了九种可能性,但与这个问题相关的三个主要可能性是
- 使用curses(3)库
- 旧的 BSD 风格系统,使用 sgttyb 设置 CBREAK 或 RAW
- Posix 或旧的 System V 风格系统,使用 TCGETAW/TCSETAW 将 c_cc[VMIN] 设置为 1,将 c_cc[VTIME] 设置为 0。
遵循 C FAQ 中的一些参考文献可能会导致 本页 kbhit 代码片段.