使用 popen 而不阻塞

use popen without blocking

在 C++ 中,我正在 运行 宁一个 bash 命令。命令是“echo | openssl s_client -connect zellowork.io:443”

但如果失败,我希望它在 4 秒内超时。当来自 c++ 代码的 运行 时,命令前的典型“/usr/bin/timeout 4 /usr/bin/sh -c”不起作用。

所以我试图创建一个函数,使用 popen 发送命令,然后最多等待 4 秒让命令在它之前完成 returns。我遇到的困难是 fgets 正在阻塞,它将等待 20 秒(在这个命令上),然后它解除阻塞并失败,而且在我调用 fgets 之前,我无论如何都找不到流中是否有要读取的内容。这是我的代码。

ExecuteCmdReturn Utils::executeCmdWithTimeout(string cmd, int ms)
{
    ExecuteCmdReturn ecr;
    ecr.success = false;
    ecr.outstr = "";

    FILE *in;
    char buff[4096];

    u64_t startTime = TWTime::ticksSinceStart();
    u64_t stopTime = startTime + ms;

    if(!(in = popen(cmd.c_str(), "r"))){
        return ecr;
    }  
    fseek(in,0,SEEK_SET);  

    stringstream ss("");
    long int lastPos = 0;
    long int newPos = 0;
    while (TWTime::ticksSinceStart() < stopTime) {
        newPos = ftell(in);
        if (newPos > lastPos) {
            lastPos = newPos;
            if (fgets(buff, sizeof(buff), in) == NULL) {
                break;
            } else {
                ss << buff;
            }
        } else {
            msSleep(10);
        }
    }

    auto rc = pclose(in);

    ecr.success = true;
    ecr.outstr = ss.str();
    return ecr;
}
  1. 使用std::async表示你可能会异步得到你的结果(一个std::future<ExecuteCmdReturn>
  2. 使用std::future<T>::wait_for超时等待结果。

这是一个例子:

首先,为您的 executeCmdWithTimeout 函数随机休眠 0 到 5 秒。

int do_something_silly()
{
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distribution(0, 5);
    auto sleep_time = std::chrono::seconds(distribution(gen));
    std::cout << "Sleeping for " << sleep_time.count() << " seconds\n";
    std::this_thread::sleep_for(sleep_time);
    return 42;
}

然后,异步启动任务并超时:

int main()
{
    auto silly_result = std::async(std::launch::async, [](){ return do_something_silly();});
    auto future_status = silly_result.wait_for(3s);
    switch(future_status)
    {
        case std::future_status::timeout:
            std::cout << "timed out\n";
            break;
        case std::future_status::ready:
            std::cout << "finished. Result is " << silly_result.get() << std::endl;
            break;
        case std::future_status::deferred:
            std::cout << "The function hasn't even started yet.\n";
    }
}

我在这里使用了 lambda,尽管我不需要这样做,因为在你的情况下它会更容易,因为看起来你正在使用成员函数并且你会想要捕获 [this]

Live Demo


在您的情况下,main 将变为 ExecuteCmdReturn Utils::executeCmdWithTimeout(string cmd, int ms),而 do_something_silly 将变为私人助手,名称类似于 executeCmdWithTimeout_impl

如果您在等待进程完成时超时,您可以选择终止进程,这样您就不会浪费任何额外的周期。


如果您发现自己创建了许多像这样的短寿命线程,请考虑使用线程池。我在 boost::thread_pool 方面取得了很大的成功(如果您最终朝这个方向发展,请考虑使用 Boost.Process 来处理您的流程创建)。