单例模式popen工具封装

1. 背景介绍

Unix/Linux环境下在C/C++中调用Native脚本命令通常使用system或者popen,二者都是先fork子进程然后execl加载代码镜像开始执行。system相比较popen较为简单,但是可能会出现"no child process"的情况,即调用的父进程没有等待子进程返回就退出了,通常的办法是使用system前置换信号量,如调用pox_system函数:

typedef void (*sighandler_t)(int);
static int pox_system(const char* cmd_line)
{
    int ret = 0;
    sighandler_t old_handler = std::signal(SIGCHLD, SIG_DFL);
    ret = std::system(cmd_line);
    signal(SIGCHLD, old_handler);
    return ret;
}

这种方式的处理在单线程工作的系统中通常OK;但是如果使用多线程并发,仍然会出现"no child process"的情况,例如在执行比较重的任务时,A线程pox_system置换了信号后system出子进程开始执行任务,恰巧此时B线程置换信号,等到A线程恢复原信号后,B线程才system产生进程,这种情况下执行环境正是A执行pox_system之前的状态,如果此时是SIG_IGN,那就瞎了...总结一下,如果pox_system的操作不是原子的,那就会概率的出现"no child process"的情况。如下图所示,先后两个pox_system调用A和B(在切换signal前后分别设为A0、A1和B0、B1):

那有什么好办法吗?加锁互斥?不嫌麻烦么?用popen吧!

2. 使用单例模式对popen工具封装

头文件声明

/* popen_util.h
#ifndef _POPEN_UTIL_H__
#define _POPEN_UTIL_H__
#define POPEN_OPEN_FAILED (-1)
#define POPEN_FORK_FAILED (-2)
#define POPEN_EXEC_CMD_FAILED (-3)
#define MAXBUFFSIZE 1024
#include <string>
class PopenUtil
{
public:
    static PopenUtil& GetInstance();
    static int DoPopen(const std::string &cmd, std::string &result);
    static int DoPopenSpecial(const std::string &cmd, std::string &result);
private:
    PopenUtil();
    ~PopenUtil();
};
#endif

源文件定义

#include <cstdio>
#include "popen_util.h"
PopenUtil::PopenUtil()
{
}
PopenUtil::~PopenUtil()
{
}
PopenUtil& PopenUtil::GetInstance()
{
    static PopenUtil popenUtil;
    return popenUtil;
}
int PopenUtil::DoPopen(const std::string &cmd, std::string &result)
{
    FILE* fd = popen(cmd.c_str(), "r");
    if(fd == NULL)
    {
        return POPEN_OPEN_FAILED;
    }
    char buf[MAXBUFFSIZE]={'\0'};
    int ret = fread(buf, sizeof(buf), 1, fd);
    if(ret >= 0)
    {
        result = buf;
    }
    if (0 != pclose(fd))
    {
        result += "pclose failed";
    }
    return ret >= 0 ? 0 : -1;
}
int PopenUtil::DoPopenSpecial(const std::string &cmd, std::string &result)
{
    std::string appendStr = cmd + std::string("; echo \"Ret code:\"$?");
    FILE* fd = popen(appendStr.c_str(), "r");
    if(fd == NULL)
    {
        return POPEN_FORK_FAILED;
    }
    char buf[MAXBUFFSIZE]={'\0'};
    while(fgets(buf, MAXBUFFSIZE, fd) != NULL)
    {
        result += std::string(buf);
    }
    if (0 != pclose(fd))
    {
        result += "pclose failed";
    }
    int ret = 0;
    if (std::string::npos == result.find("Ret code:0"))
    {
        ret = POPEN_EXEC_CMD_FAILED;
    }
    return ret;
}

使用示例

#include "popen_util.h"
#include <iostream>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <cerrno>
int main(int argc, char *argv[])
{
    if (argc == 1)
    {
        std::cout << "usage: " << argv[0] << " comands ..." << std::endl;
        return -1;
    }
    std::string cmd;
    for (int i = 1; i < argc; i++)
    {
        cmd += argv[i];
        if (i != argc - 1)
        {
            cmd += " ";
        }
    }
    std::string result;
    int status = PopenUtil::GetInstance().DoPopenSpecial(cmd, result);
    if (POPEN_OPEN_FAILED == status || POPEN_FORK_FAILED == status || POPEN_EXEC_CMD_FAILED == status)
    {
        perror("popen failed");
    }
    std::string output = "popen execute command: " + cmd +", "
        + "error: " + strerror(errno) + ", "
        + "result: " + result;
    std::cout << std::endl << "--> " << output << std::endl << std::endl;
    return status;
}
posted @ 2016-08-21 13:04  再见悟空  阅读(462)  评论(0编辑  收藏  举报