如何在 C 语言的 Linux/OS X 上执行非阻塞控制台 IO?
我想补充一个例子:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char const *argv[]) {
char buf[20];
fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
sleep(4);
int numRead = read(0, buf, 4);
if (numRead > 0) {
printf("You said: %s", buf);
}
}
运行此程序时,您有 4 秒的时间向标准输入提供输入。如果未找到输入,它将返回。
2 样本执行:
$ ./a.out
fda
You said: fda
$ ./a.out
$
像 Pete Kirkham,我找到了 cc.byexamples.com,它对我有用。去那里找到对问题的很好的解释,以及 ncurses 版本。
我的代码需要从标准输入或文件中获取初始命令,然后在处理初始命令时观察取消命令。我的代码是 C++,但你应该可以使用
scanf()
以及我使用 C++ 输入函数的其余部分 getline()
.
肉是检查是否有可用输入的函数:
#include <unistd.h>
#include <stdio.h>
#include <sys/select.h>
// cc.byexamples.com calls this int kbhit(), to mirror the Windows console
// function of the same name. Otherwise, the code is the same.
bool inputAvailable()
{
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
return (FD_ISSET(0, &fds));
}
这必须在任何标准输入函数之前调用当我在使用此函数之前使用
std::cin
时,它再也没有返回true。例如,main()
有一个看起来像这样的循环:
int main(int argc, char* argv[])
{
std::string initialCommand;
if (argc > 1) {
// Code to get the initial command from a file
} else {
while (!inputAvailable()) {
std::cout << "Waiting for input (Ctrl-C to cancel)..." << std::endl;
sleep(1);
}
std::getline(std::cin, initialCommand);
}
// Start a thread class instance 'jobThread' to run the command
// Start a thread class instance 'inputThread' to look for further commands
return 0;
}
在输入线程中,新命令被添加到队列中,由
jobThread
定期处理。 inputThread
看起来有点像这样:
THREAD_RETURN inputThread()
{
while( !cancelled() ) {
if (inputAvailable()) {
std::string nextCommand;
getline(std::cin, nextCommand);
commandQueue.lock();
commandQueue.add(nextCommand);
commandQueue.unlock();
} else {
sleep(1);
}
}
return 0;
}
这个功能可能在
main()
中,但我正在使用现有的代码库,而不是反对它。
对于我的系统,在发送换行符之前没有可用的输入,这正是我想要的。如果您想在键入时读取每个字符,则需要关闭标准输入上的“规范模式”。 cc.byexamples.com 有一些我没有尝试过的建议,但其余的都有效,所以它应该有效。
你没有,真的。 TTY(控制台)是一个非常有限的设备,您几乎不执行非阻塞 I/O。当你看到一些看起来像非阻塞 I/O 的东西时,你会做什么,比如在 curses/ncurses 应用程序中,称为 raw I/O。在原始 I/O 中,没有字符的解释,没有擦除处理等。相反,您需要编写自己的代码来检查数据,同时做其他事情。
在现代 C 程序中,您可以通过将控制台 I/O 放入 thread 或轻量级进程中,以另一种方式简化它。然后 I/O 可以以通常的阻塞方式继续,但数据可以插入到队列中以在另一个线程上处理。
这里有一个 curses 教程 涵盖了更多内容。
本月早些时候我在“没有 ncurses 的循环中非阻塞用户输入”添加了书签,当时我认为我可能需要非阻塞、非缓冲的控制台输入,但我没有,所以不能保证它是否有效或不。对于我的使用,我不关心它在用户按下回车之前没有得到输入,所以只使用 aio 来读取标准输入。
GNU Readline,特别是其中允许您注册回调函数的部分。那么模式就是:
perf/builtin-top.c 来源(简体):
static void *display_thread(void *arg)
{
struct pollfd stdin_poll = { .fd = 0, .events = POLLIN };
struct termios save;
set_term_quiet_input(&save);
while (!done) {
switch (poll(&stdin_poll, 1, delay_msecs)) {
...
}
}
tcsetattr(0, TCSAFLUSH, &save);
}
所以,如果你想检查是否有任何数据可用,你可以像这样使用 poll() 或 select() :
#include <sys/poll.h>
...
struct pollfd pfd = { .fd = 0, .events = POLLIN };
while (...) {
if (poll(&pfd, 1, 0)>0) {
// data available, read it
}
...
}
在这种情况下,在按下 [RETURN] 键后,您将不会在每个键上收到事件,而是在整行上收到事件。这是因为终端在 canonical 模式下运行(输入流被缓冲,当按下 [RETURN] 时缓冲区刷新):
在规范输入处理模式下,终端输入在 由换行符终止的行(' ')、EOF 或 EOL 字符。无输入 可以阅读,直到用户输入了整行,并且 读取函数(参见输入和输出原语)最多返回一个 单行输入,无论请求多少字节。如果您想立即阅读字符,可以使用
#include <termios.h>
void set_term_quiet_input()
{
struct termios tc;
tcgetattr(0, &tc);
tc.c_lflag &= ~(ICANON | ECHO);
tc.c_cc[VMIN] = 0;
tc.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &tc);
}
简单程序(游乐场链接):
#include <stdio.h>
#include <unistd.h>
#include <sys/poll.h>
#include <termios.h>
void set_term_quiet_input()
{
struct termios tc;
tcgetattr(0, &tc);
tc.c_lflag &= ~(ICANON | ECHO);
tc.c_cc[VMIN] = 0;
tc.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &tc);
}
int main() {
struct pollfd pfd = { .fd = 0, .events = POLLIN };
set_term_quiet_input();
while (1) {
if (poll(&pfd, 1, 0)>0) {
int c = getchar();
printf("Key pressed: %c \n", c);
if (c=='q') break;
}
usleep(1000); // Some work
}
}
如果您正在从 STDIN 读取,则需要跳过 fread() 并使用 read() 和 write(),以及 poll() 或 select() 以防止调用阻塞。您可以使用 setbuf() 禁用输入缓冲,这会导致 fread 返回 EOF,但我从未尝试过。