C++中使用popen管道执行"ps aux"命令后,pclose关闭不正确

3

我正在运行 MacOS,想要通过我的应用程序执行 "ps aux" 命令并获取其输出。我已经编写了一个使用 popen 函数执行命令的方法:

std::string exec(const char* cmd) {

    char buffer[128];
    std::string result = "";

    FILE* pipe = popen(cmd, "r");
    if (!pipe) throw std::runtime_error("popen() failed!2");
    try {
        while (!feof(pipe)) {
            if (fgets(buffer, 128, pipe) != NULL)
                result += buffer;
        }
    } catch (...) {
        pclose(pipe);

        throw;
    }
    pclose(pipe);


    return result;
}

我有一个循环,不断运行exec("ps aux")函数。问题在于popen的管道没有关闭,我使用终端的"lsof"命令进行了检查。大约20秒后,应用程序会打开300个以上的文件描述符,这会阻止应用程序从循环中打开更多的管道(运行"ps aux"命令)。
我发现,exec函数对其他命令(管道正确关闭)也能正常工作,例如"netstat",因此必须是"ps aux"命令中的某些内容导致了管道无法关闭。
我已经搜索了很多关于这个问题的解决方案,但没有找到任何答案。请问有人可以指点我正确的方向吗?
谢谢!

3
也许与您的问题无关,但请花些时间阅读“为什么‘while(!feof(file))’总是错的?”(英文网页链接:https://dev59.com/jG035IYBdhLWcg3wbPU5) - Some programmer dude
1
此外,虽然不相关,但我个人会使用RAII类来代替显式的catch/rethrow,例如struct PipeCloser { void operator()(FILE* f) { pclose(f); } }; using PipeUniquePtr = std::unique_ptr<FILE, PipeCloser>; - Daniel Schepler
需要更多的 [mcve]。 - melpomene
1个回答

0

我看不出你的代码具体有什么问题。对于这些情况,我使用一个自定义删除器和 std::unique_ptr 一起使用,以确保文件在所有可能的退出点都会关闭。

另外请注意,不建议使用 while(eof(...)) 进行循环,原因有几个。其中一个是在发生错误时 eof 不会被设置。更多信息请参见此处

// RAII piped FILE*

// custom deleter for unique_ptr
struct piped_file_closer
{
    void operator()(std::FILE* fp) const { pclose(fp); }
};

// custom unique_ptr for piped FILE*
using unique_PIPE_handle = std::unique_ptr<std::FILE, piped_file_closer>;

//
unique_PIPE_handle open_piped_command(std::string const& cmd, char const* mode)
{
    auto p = popen(cmd.c_str(), mode);

    if(!p)
        throw std::runtime_error(std::strerror(errno));

    return unique_PIPE_handle{p};
}

// exception safe piped reading
std::string piped_read(std::string const& cmd)
{
    std::string output;

    if(auto pipe = open_piped_command(cmd, "r"))
    {
        char buf[512];
        while(auto len = std::fread(buf, sizeof(char), sizeof(buf), pipe.get()))
            output.append(buf, len);

        if(std::ferror(pipe.get()))
            throw std::runtime_error("error reading from pipe");
    }

    return output;
}

在我的系统上调用 auto output = piped_read("ps aux"); 几百次并不会产生与此代码相关的错误。


谢谢您的帮助!然而,使用那段代码后我仍然遇到了问题——应用程序崩溃,并显示以下信息: "Application Specific Information: abort() called terminating with uncaught exception of type std::runtime_error: Too many open files" - MrWhite
运行它数百次不会导致应用程序崩溃,但是如果运行它300次,因为MacOS每个进程打开的文件描述符的默认限制为256(如果我记得正确),那么肯定会导致崩溃。 - MrWhite
@MrWhite 在每次调用后,该调用的文件描述符应关闭,因此您应该可以随意运行该命令多次。我猜您的问题可能出在其他地方?也许是一些微妙的未定义行为?我现在正在认真进行压力测试,而在不断循环您的命令时,文件描述符计数没有发生变化。 - Galik
1
@MrWhite 现在我正在对你的代码进行压力测试,并得到相同的结果,在我的机器上没有任何问题。也许你发布的代码很好,问题可能是其他方面的原因? - Galik
1
非常抱歉,我没有在循环中关闭一个套接字,如果成功执行了“ps aux”命令,那就会导致文件描述符的数量增加 :/ - MrWhite

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接