使用 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;
}
- 使用
std::async
表示你可能会异步得到你的结果(一个std::future<ExecuteCmdReturn>
)
- 使用
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 来处理您的流程创建)。
在 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;
}
- 使用
std::async
表示你可能会异步得到你的结果(一个std::future<ExecuteCmdReturn>
) - 使用
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 来处理您的流程创建)。