为何调用printf("new data: ")会无限打印,另一个printf却正常阻塞?
问题:read()监听文件新数据时的异常现象分析
需求是通过read()阻塞线程,直到目标文件写入新数据。现有如下C语言代码:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> void sigint_handler(int sig) { printf("received SINT, exiting gracefully, com mto carinho...\n"); exit(0); } int main() { signal(SIGINT, sigint_handler); char buffer[256] = { 0 }; int fd = open("foo.txt", O_RDONLY); if(fd == -1) { printf("error opening file\n"); return -1; } while(1) { ssize_t nbytes = read(fd, buffer, sizeof(buffer)); if(nbytes == -1) { printf("error reading file\n"); close(fd); return 1; } //printf("new data: "); printf("%.*s", (int) nbytes, buffer); } close(fd); return 0; }
运行时出现两种异常情况:
- 启用注释的
printf("new data: ")时,程序会无限打印该字符串 - 仅保留
printf("%.*s", (int)nbytes, buffer)时,程序打印完已有内容后阻塞,但无法响应文件新写入的数据
原因分析
1. 无限打印"new data: "的原因
当read()读取到文件末尾(EOF)时,会返回0而非阻塞。此时启用printf("new data: ")后,循环会无休无止地执行:
- 文件初始内容读完后,后续每次
read()都返回0 - 代码中没有处理
nbytes == 0的情况,循环直接进入下一次read(),同时每次都执行printf("new data: "),导致无限输出
2. 仅保留%.*s时阻塞但不响应新数据的原因
当read()返回0(EOF)时,printf("%.*s", 0, buffer)不会输出任何内容,此时循环再次调用read()。但普通磁盘文件的read()在到达EOF后会直接返回0,不会阻塞等待新数据——只有管道、FIFO、套接字这类特殊文件描述符,read()才会在EOF后阻塞等待新写入。常规文件不支持这种"监听新数据"的阻塞行为。
修复方法
要实现监听普通文件新数据的需求,需要结合文件偏移量检测或Linux原生的文件事件监听机制,以下是两种可行方案:
方案1:基于文件偏移量的轮询(简单实现)
每次读取后记录文件偏移量,定期检查文件大小变化,若文件变大则移动偏移量并读取新内容:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <sys/stat.h> void sigint_handler(int sig) { printf("received SIGINT, exiting gracefully...\n"); exit(0); } int main() { signal(SIGINT, sigint_handler); char buffer[256] = {0}; int fd = open("foo.txt", O_RDONLY); if(fd == -1) { printf("error opening file\n"); return -1; } off_t current_offset = lseek(fd, 0, SEEK_END); // 初始定位到文件末尾 struct stat file_stat; while(1) { stat("foo.txt", &file_stat); if(file_stat.st_size > current_offset) { // 文件有新内容,移动偏移量并读取 lseek(fd, current_offset, SEEK_SET); ssize_t nbytes = read(fd, buffer, sizeof(buffer)-1); if(nbytes > 0) { buffer[nbytes] = '\0'; printf("new data: %s", buffer); current_offset += nbytes; } } sleep(1); // 轮询间隔,避免占用过高CPU } close(fd); return 0; }
方案2:使用inotify机制(Linux原生高效方案)
利用Linux的inotify API监听文件的修改事件,当文件有新写入时再触发读取:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <sys/inotify.h> #include <sys/stat.h> void sigint_handler(int sig) { printf("received SIGINT, exiting gracefully...\n"); exit(0); } int main() { signal(SIGINT, sigint_handler); int fd = open("foo.txt", O_RDONLY); if(fd == -1) { printf("error opening file\n"); return -1; } int inotify_fd = inotify_init(); if(inotify_fd == -1) { perror("inotify_init failed"); close(fd); return -1; } // 添加对foo.txt的修改事件监听 int watch_fd = inotify_add_watch(inotify_fd, "foo.txt", IN_MODIFY); if(watch_fd == -1) { perror("inotify_add_watch failed"); close(fd); close(inotify_fd); return -1; } char buffer[256] = {0}; off_t current_offset = lseek(fd, 0, SEEK_END); while(1) { struct inotify_event event; // 阻塞等待文件事件 ssize_t bytes_read = read(inotify_fd, &event, sizeof(event)); if(bytes_read == -1) { perror("read inotify event failed"); break; } if(event.mask & IN_MODIFY) { // 文件被修改,读取新内容 lseek(fd, current_offset, SEEK_SET); ssize_t nbytes = read(fd, buffer, sizeof(buffer)-1); if(nbytes > 0) { buffer[nbytes] = '\0'; printf("new data: %s", buffer); current_offset += nbytes; } } } inotify_rm_watch(inotify_fd, watch_fd); close(inotify_fd); close(fd); return 0; }
编译命令:gcc filename.c -o filename(Linux环境默认支持inotify)
内容的提问来源于stack exchange,提问作者Coder
相关产品推荐
相关产品推荐

