如何在Linux/OS X上使用C语言进行非阻塞控制台IO操作?
如何在Linux/OS X上使用C语言进行非阻塞控制台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);
}
}
$ ./a.out
fda
You said: fda
$ ./a.out
$
stdin
可以与未来的进程共享,这些进程可能不希望stdin
是非阻塞的。在运行程序后尝试使用cat
。并且,请使用一致的缩进风格。 - 12431234123412341234123和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));
}
在使用任何stdin输入函数之前,必须调用此函数。当我在使用这个函数之前使用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有一些建议,我还没有尝试过,但其余的方法都可以工作,所以应该也可以。
在现代C程序中,你可以通过将控制台I/O放入线程或轻量级进程中来简化此过程。然后,I/O可以按照通常的阻塞方式进行,但数据可以被插入队列以在另一个线程中进行处理。
这里有一个curses教程,更详细地介绍了它。
fcntl(0, F_SETFL, fcntl(0, GETFL) | O_NONBLOCK)
命令——尽管通常不建议这样做,因为它会困惑其他使用标准输入的人,但它确实能够工作。 - ephemient让我们看看Linux工具中的一个例子。例如,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
}
...
}
如果您想立即读取字符,则可以使用非规范模式。使用tcsetattr()进行切换:在规范输入处理模式中,终端输入以由换行符('\n'),EOF或EOL字符终止的行进行处理。在用户键入整行之前,无法读取任何输入,并且read函数(请参阅输入和输出基元)返回最多一个输入行,无论请求多少字节。
#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
}
}
select()
和poll()
进行异步I/O,并且还涵盖了终端模式问题。点赞+1。 - reichhart我不太确定你所说的“控制台IO”是什么意思——你是从STDIN读取,还是这是一个从其他来源读取的控制台应用程序?
如果你正在从STDIN读取,你需要跳过fread()并使用read()和write(),并使用poll()或select()来防止调用被阻塞。你可以尝试禁用输入缓冲区,这应该会导致fread返回EOF,使用setbuf()即可,但我从未尝试过。