跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

c++怎么实现类似于tail命令的实时日志监控_轮询检测文件末尾【进阶】

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

相关文章