用fcntl设置非阻塞模式是唯一可靠方式,需先F_GETFL读取原标志,再按位或O_NONBLOCK后F_SETFL设置,避免覆盖其他标志,且普通文件设该标志无效。

Linux下用fcntl设置文件描述符为非阻塞模式
直接调用fcntl设置O_NONBLOCK是唯一可靠方式,open时传入该标志只对新打开的文件有效,已有fd必须用fcntl修改。
常见错误是以为open(fd, O_RDWR | O_NONBLOCK)能改已打开的fd——不行,open只用于打开新文件,参数对旧fd无效。
实操建议:
- 先用
fcntl(fd, F_GETFL)读取当前标志位,再用|= O_NONBLOCK按位或设置,避免覆盖其他标志(如O_APPEND) - 设置后务必检查返回值:失败时
fcntl返回-1,errno设为EBADF(非法fd)或EINVAL(不支持非阻塞) - 套接字、管道、终端设备通常支持
O_NONBLOCK;普通磁盘文件在Linux上也能设,但read/write仍会阻塞——这点极易被忽略
为什么对普通文件设O_NONBLOCK基本没用
Linux内核对常规文件(ext4/xfs等上的regular file)忽略O_NONBLOCK。调用read或write时,只要数据在页缓存中就立刻返回,否则同步回刷/加载,不会因“等待IO完成”而阻塞——所以非阻塞模式无实际意义。
立即学习“C++免费学习笔记(深入)”;
真正需要非阻塞的是以下场景:
- 网络套接字(
socket、accept返回的fd) - 管道(
pipe、fifo) - 伪终端(
/dev/pts/*) - 某些设备文件(如
/dev/ttyS0)
示例:检测socket是否可读而不阻塞
int flags = fcntl(sockfd, F_GETFL);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
ssize_t n = read(sockfd, buf, sizeof(buf));
if (n == -1 && errno == EAGAIN) {
// 无数据可读,但fd是正常的非阻塞状态
}
O_NONBLOCK与ioctl(fd, FIONBIO, &on)的区别
两者都能设非阻塞,但行为不同:
-
fcntl(fd, F_SETFL, O_NONBLOCK):标准POSIX接口,推荐首选 -
ioctl(fd, FIONBIO, &on):BSD遗留接口,on为非零即开,为0即关;部分系统(如旧版Solaris)不支持fcntl方式 - 关键差异:
ioctl是“开关式”,直接覆盖;fcntl是“位操作式”,需手动保留原有标志 - 混用风险:先用
ioctl设非阻塞,再用fcntl读标志可能读不到O_NONBLOCK(因ioctl不更新F_GETFL可见的标志位)
C++封装非阻塞IO时容易踩的坑
用RAII封装fcntl设置/恢复非阻塞状态时,最常漏掉两点:
- 构造函数里没保存原始标志(
F_GETFL结果),导致析构时无法准确还原,可能意外关闭O_APPEND等关键标志 - 没处理
fcntl失败的边界情况(如传入标准输入STDIN_FILENO,某些环境不允许设非阻塞) - 对同一fd多次构造该对象,会重复设置
O_NONBLOCK,虽无害但冗余;更糟的是,若析构顺序错乱,可能把别人设的非阻塞给关了
简单修复思路:用std::optional<int>缓存原始flags,只在首次设置时读取;析构前检查当前flags是否真含O_NONBLOCK再清除。
非阻塞IO的核心约束始终没变:它只改变“等待IO就绪”这一环节的行为,不加速IO本身;对文件来说,这层控制权根本不在用户态代码手里。


















