使用popen调用持续输出命令时fgets阻塞30秒的问题排查
解决popen调用持续输出命令时fgets阻塞的问题
这个问题我熟——你碰到的是管道输出缓冲的坑!当你用popen调用像rostopic hz /topicname这类持续输出的命令时,默认情况下程序的标准输出会采用全缓冲模式(而非终端环境下的行缓冲),只有当缓冲区被填满(或者进程终止)时,数据才会被刷到管道里,这就是你要等30秒才能看到内容的原因,而且fgets会一直阻塞等待数据到来。
核心原因拆解
在Linux/Unix系统中,程序的输出缓冲策略取决于输出目标:
- 当输出到终端时,默认是行缓冲——每输出一行就立刻刷新到屏幕;
- 当输出到管道、文件等非终端设备时,默认是全缓冲——要等缓冲区(通常是4KB或8KB)满了才会刷新数据。
rostopic hz每秒只输出一行,远填不满缓冲区,所以数据会一直攒着,直到系统强制刷新(约30秒)或者进程结束。
最简单的解决方案:强制行缓冲
我们可以用stdbuf工具来修改命令的输出缓冲模式,让它强制使用行缓冲。把原命令包装成:
stdbuf -oL rostopic hz /topicname
其中-oL表示将标准输出(stdout)设置为行缓冲模式。
修改后的完整代码
同时我也修正了你原代码里的语法错误(pclose的位置不对),并增加了基本的错误判断:
std::string MyClass::InvokeCmd(std::string command) { std::string result; std::array<char, 128> buffer; // 用stdbuf强制命令输出行缓冲 std::string wrapped_command = "stdbuf -oL " + command; FILE *pipe = popen(wrapped_command.c_str(), "r"); // 处理管道打开失败的情况 if (!pipe) { return ""; } // 现在可以实时读取每一行输出了 while (fgets(buffer.data(), buffer.size(), pipe) != NULL) { result += buffer.data(); // 如果需要实时处理输出,比如打印到控制台,在这里加逻辑即可 // std::cout << "Received: " << buffer.data(); } pclose(pipe); return result; }
额外注意事项
- 如果你的系统没有
stdbuf(比如一些嵌入式环境),可以尝试用unbuffer命令替代,用法类似:unbuffer rostopic hz /topicname; rostopic hz是持续运行的命令,原代码的while循环会一直阻塞直到进程被终止。如果需要自动停止,可以在代码里加入超时逻辑,或者在另一个线程中通过进程ID杀死该命令;- 如果你有权限修改
rostopic的源码,也可以在程序里手动设置setvbuf(stdout, NULL, _IOLBF, 0)来强制行缓冲,但显然用stdbuf是最不需要改动源码的通用方案。
内容的提问来源于stack exchange,提问作者Gil404
相关产品推荐
相关产品推荐

