应改用 lseek(POSIX)或 _lseeki64(Windows)绕过 stdio 缓冲直接操作文件描述符,每次轮询前 lseek(fd, 0, SEEK_END) 获取真实大小并与上次 offset 比较,大小变化则 lseek(fd, last_offset, SEEK_SET) 读增量;Linux 优先 inotify,Windows 必须 ReadDirectoryChangesW;logrotate 场景需监听 inode 变更并重开文件。
如何用
+
安全地定位文件末尾
直接
看似简单,但对正在被写入的日志文件(尤其是多进程/多线程追加)极易出错:某些系统下
可能返回不准确的偏移,或
后
读不到新内容。根本原因是标准库 FILE 流有内部缓冲,且未同步底层文件状态。
实操建议:
改用
(POSIX)或
(Windows)绕过 stdio 缓冲,直接操作文件描述符
每次轮询前先
获取真实大小,再与上次记录的
比较
若大小不变,sleep 后重试;若变大,
再读增量
注意:Windows 上需用
配合
,不能混用
和
为什么
/
比轮询更可靠
轮询本质是“猜”文件是否变化,而 inotify(Linux)和 ReadDirectoryChangesW(Windows)是内核通知机制,事件触发即读,无延迟、无遗漏、不耗 CPU。
常见错误现象:
立即学习
“
C++免费学习笔记(深入)
”;
轮询间隔设为 100ms,但日志每 50ms 写一行 → 丢行
轮询时文件被 logrotate 切走,
失败却没检查返回值 → 无限循环报错
使用场景:
C知道
CSDN推出的一款AI技术问答工具
下载
Linux 下优先用
监听追加
Windows 下必须用
,注意它监听目录而非单个文件,需过滤出目标文件名
两者都需非阻塞或搭配
/
使用,避免卡死
的
和
在实时读取中容易失效
很多人试图用
配合
+
实现“看看有没有新内容”,结果发现
总返回
,或
为 0 —— 这是因为
默认启用缓冲,且不会自动响应外部文件增长。
参数差异与规避方式:
构造时不加
,换行符处理可能干扰偏移计算
调用
可清除
,但无法恢复已错失的数据流位置
真正可行的做法:关闭流,用
重新打开(Linux 下仍可读原 inode),或干脆弃用
,改用
系统调用
logrotate 场景下如何不丢日志、不重复读
这是最常被忽略的复杂点:logrotate 会 rename 原文件并创建新空文件,但你的程序还持着旧 fd。此时
返回的是旧文件大小(即使它已被 mv 走),新内容全写进新文件,你完全感知不到。
解决方案只有两个方向:
监听文件名变更:inotify 的
+
组合可捕获 rename 和新建动作,但需维护文件名到 fd 的映射
放弃监控文件名,改监控 inode:用
比较
,一旦不一致,就
旧 fd、
新路径(注意路径可能已变)
Windows 下无 inode 概念,只能靠定时
对比
+
实际项目里,inode 方案更通用,但必须在每次读前做一次 stat 校验,不能只在启动时查一次。
fseekftellfseek(fp, 0, SEEK_END)ftellfseekfreadlseek_lseeki64lseek(fd, 0, SEEK_END)offsetlseek(fd, last_offset, SEEK_SET)_open_readfopenlseekinotifyReadDirectoryChangesWlseekinotify_add_watch(fd, path, IN_MODIFY)ReadDirectoryChangesWselectepollstd::ifstreampeek()gcount()std::ifstreamseekgpeekpeek()EOFgcount()ifstreamifstreamstd::ios::binaryclear()failbitopen()ifstreamread()lseek(fd, 0, SEEK_END)IN_MOVED_FROMIN_CREATEstat()st_inocloseopenGetFileInformationByHandledwVolumeSerialNumbernFileIndexHigh/Low