Ubuntu 20.04下fgets无法二次读取命名管道内容问题咨询
问题解答
1. 代码失效原因
该问题不是发行版基础库变更导致fgets本身功能异常,核心原因是命名管道的I/O特性和C标准文件流的EOF状态处理规则:
- 每次你执行
echo "message" >> /tmp/pipe写入数据时,echo进程会临时打开管道写入端,写完数据后进程退出,此时管道的所有写入端被关闭,读取端的fgets读完当前数据后会触发EOF,返回NULL,同时会给result_pipe_stream设置EOF错误标志。 - 按照C标准规定,文件流一旦被标记了EOF/错误状态,后续所有读操作都会直接返回失败,除非手动清除该标志。
- Ubuntu 18.04携带的glibc 2.27版本对管道这类特殊文件的EOF标志处理存在非标准兼容逻辑,当管道有新写入端连接时会自动重置标志,而Ubuntu 20.04使用的glibc 2.31版本修复了这个不符合标准的行为,因此原有代码无法正常运行。
2. 可行解决方案
可根据使用场景选择以下任意一种方案:
- 方案1:手动清除流错误标志
每次fgets返回NULL时调用clearerr()清除流的错误标志,再加短休眠避免空转占用CPU。需要注意使用该方案要提前引入<unistd.h>头文件,修改后的read_result_from_pipe函数逻辑如下:string read_result_from_pipe(){ if (result_pipe_stream == NULL){ printf("\n[BINDING-COMMONS] ERROR: Pipe is not set"); return ""; } std::stringstream oss; while (1) { char buf[BUFSIZ]; if( fgets (buf, BUFSIZ, result_pipe_stream) != NULL ) { int buflen = strlen(buf); if (buflen >0){ if (buf[buflen-1] == '\n'){ buf[buflen-1] = '\0'; oss << buf; return oss.str(); } else { oss << buf; // line was truncated. Read another block to complete line. } } } else { clearerr(result_pipe_stream); usleep(10000); // 休眠10ms } } } - 方案2:以读写模式打开管道
修改main函数中fopen的打开模式,从只读"r"改为读写"r+",让进程自身持有管道的写入端,只要进程不退出,管道就永远不会出现所有写入端关闭的情况,也就不会触发EOF状态,读不到数据时fgets会主动阻塞等待新数据写入,无需额外处理错误标志:int main(int argc, char *argv[]){ result_pipe_stream = fopen("/tmp/pipe" , "r+"); while (1){ cout << read_result_from_pipe() << '\n'; } } - 方案3:改用底层系统调用
替换C标准库的文件流接口,直接使用open/read系统调用读取管道,底层系统调用不会保留EOF状态,每次读取都会直接尝试从管道获取数据。
内容的提问来源于stack exchange,提问作者Francesc Lordan
相关产品推荐
相关产品推荐

