在Linux环境中使用C++进行系统编程时,文件的默认读写操作通常是阻塞的,也就是说当没有数据可读或缓冲区满时,read或write调用会一直等待。通过fcntl函数配合O_NONBLOCK标志,我们可以将一个已经打开的文件描述符设置为非阻塞模式,使IO调用在没有就绪时立即返回错误而不是挂起。

为什么需要非阻塞模式
阻塞读写会让当前线程停在系统调用里,在多线程服务中容易导致 worker 被占满。非阻塞模式结合 select、poll 或 epoll 使用,可以实现单线程管理大量连接,是网络编程中的常见做法。
fcntl 与 O_NONBLOCK 的基本用法
fcntl 用来操作文件描述符的属性。设置非阻塞的典型步骤是:先获取原有 flags,再用按位或运算加上 O_NONBLOCK,最后写回。
获取并设置非阻塞标志
下面代码展示如何把标准输入设为非阻塞,并演示读不到数据时的返回情况。
#include <unistd.h>
#include <fcntl.h>
#include <iostream>
#include <cstring>
int main() {
// 假设 fd 是已打开的文件描述符,这里用 0 代表标准输入做演示
int fd = 0;
// 1. 获取当前文件状态标志
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
std::cerr << "fcntl get failed: " << strerror(errno) << std::endl;
return 1;
}
// 2. 添加 O_NONBLOCK 标志
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
std::cerr << "fcntl set failed: " << strerror(errno) << std::endl;
return 1;
}
// 3. 尝试非阻塞读取
char buf[64];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = ' ';
std::cout << "read: " << buf << std::endl;
} else if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
std::cout << "no data available now, would block" << std::endl;
} else {
std::cerr << "read error: " << strerror(errno) << std::endl;
}
}
return 0;
}
恢复为阻塞模式
若想恢复成阻塞模式,只需把 O_NONBLOCK 位去掉再写回即可。
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
void set_blocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return;
flags &= ~O_NONBLOCK; // 清除非阻塞位
fcntl(fd, F_SETFL, flags);
}
int main() {
int fd = 1; // 标准输出
set_blocking(fd);
std::cout << "fd set to blocking mode" << std::endl;
return 0;
}
常见注意事项
- 非阻塞 read 或 write 返回 -1 且 errno 为 EAGAIN 或 EWOULDBLOCK 时,表示当前资源未就绪,应稍后重试或使用 IO 多路复用等待。
- 管道、socket、终端都支持 O_NONBLOCK,但普通磁盘文件在大多数 Linux 文件系统上即使设置非阻塞也不会真正异步,只是立即返回。
- 用 open 打开文件时也可以直接传入 O_NONBLOCK,例如 open("test.txt", O_RDONLY | O_NONBLOCK)。
小结
通过 fcntl 的 F_GETFL 与 F_SETFL 命令操作 O_NONBLOCK 标志,是 C++ 在 Linux 下控制文件描述符阻塞行为的标准做法。把它和事件驱动框架配合使用,可以写出更健壮的高并发程序。
C++fcntlO_NONBLOCK修改时间:2026-07-30 23:30:23