栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

Implementing a Bidirectional Communication popen() Using Pipes

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Implementing a Bidirectional Communication popen() Using Pipes

Though this is much easier to do if we use UNIX sockets : )
Let’s cut to the chase and go straight to the code (like we always do :^)

Header file popen2.h:

#ifndef POPEN2_H
#define POPEN2_H

#include 
#include 

// We might also want to use these in C, or provide an interface
// for high-level languages like python (though pybind may seem
// to be a better option, or is it? :).
#ifdef __cplusplus
extern "C" {
#endif

// kinda miss default values
struct subprocess_t {
    pid_t cpid;
    int exit_code;
    FILE* p_stdin;  // child process's stdin, writable
    FILE* p_stdout; // child process's stdout, readable
};

// though I think calling it `popen1` will be more appropriate :)

struct subprocess_t popen2(const char* program);

int pclose2(subprocess_t* p);

#ifdef __cplusplus
}
#endif

#endif // POPEN2_H

Implementation file popen2.cc:

#include "popen2.h"
#include 
#include 
#include 
#include 
#include 

void init_subprocess(subprocess_t* p)
{
    p->cpid = -1;
    p->exit_code = 0;
    p->p_stdin = nullptr;
    p->p_stdout = nullptr;
}

// thread unsafe :)
static std::list opened_processes;

subprocess_t popen2(const char* program)
{
    subprocess_t subprocess;
    // although already initialized (in C++), but maybe not what we want
    init_subprocess(&subprocess);
    // We will need 2 pipes in order to communicate between parent and child.
    // If only using 1 pipe, both child and parent will hold two ends of the
    // pipe, and now if the parent is going to write to the pipe, data may
    // get read from parent immediately, without going to the child.
    int pipe_pc[2];  // write: P -> C  /  read: C <- P
    int pipe_cp[2];  // write: C -> P  /  read: P <- C
    if (pipe(pipe_pc) < 0) {
        return subprocess;
    }
    if (pipe(pipe_cp) < 0) {
        close(pipe_pc[0]);
        close(pipe_pc[1]);
        return subprocess;
    }

    pid_t pid = fork();
    if (pid == -1) { // failed
        close(pipe_pc[0]);
        close(pipe_pc[1]);
        close(pipe_cp[0]);
        close(pipe_cp[1]);
        return subprocess;
    }
    else if (pid == 0) { // child
        // close unrelated files inherited from parent
        for (const auto& p : opened_processes) {
            close(fileno(p.p_stdin));
            close(fileno(p.p_stdout));
        }

        close(pipe_cp[0]); // close unused read  end in pipe C -> P
        close(pipe_pc[1]); // close unused write end in pipe P -> C

        // redirect stdout to write end of the pipe
        if (pipe_cp[1] != STDOUT_FILENO) {
            dup2(pipe_cp[1], STDOUT_FILENO);
            close(pipe_cp[1]);
        }
        // redirect stdin to read end of the pipe
        if (pipe_pc[0] != STDIN_FILENO) {
            dup2(pipe_pc[0], STDIN_FILENO);
            close(pipe_pc[0]);
        }

        // launch the shell to run the program
        const char* argp[] = {"sh", "-c", program, nullptr};
        execve("/bin/sh", (char**) argp, environ);
        // shouldn't be reached here
        _exit(127);
    }
    close(pipe_pc[0]); // close unused read  end in pipe P -> C
    close(pipe_cp[1]); // close unused write end in pipe C -> P
    // parent (client) reads from the subprocess's stdout and
    //                 writes to the subprocess's stdin
    subprocess.p_stdout = fdopen(pipe_cp[0], "r");
    subprocess.p_stdin  = fdopen(pipe_pc[1], "w");
    subprocess.cpid = pid;
    opened_processes.push_back(subprocess);
    return subprocess;
}

int pclose2(subprocess_t* p)
{
    auto it = std::find_if(opened_processes.begin(), opened_processes.end(),
                           [&](const subprocess_t& process) {
                               return process.cpid == p->cpid;
                           });
    if (it == opened_processes.end()) {
        return -1;
    }

    fclose(p->p_stdin);
    fclose(p->p_stdout);

    pid_t pid = p->cpid;
    int pstat;
    do {
        pid = waitpid(pid, &pstat, 0);
    } while (pid == -1 && errno == EINTR);

    // obtain the subprocess's exit code
    if (WIFEXITED(pstat)) {
        p->exit_code = WEXITSTATUS(pstat);
    }
    else if (WIFSIGNALED(pstat)) {
        p->exit_code = WTERMSIG(pstat);
    }

    opened_processes.erase(it);
    return (pid == -1 ? -1 : pstat);
}

Test client:

#include "popen2.h"
#include 

void write_data(subprocess_t& p, const char* data)
{
    // write data to p's stdin
    fprintf(p.p_stdin, "%s", data);
    fflush(p.p_stdin); // IMPORTANT, to make sure child receives the data
}

void read_data(const subprocess_t& p, char* data)
{
    // read data from p's stdout
    fscanf(p.p_stdout, "%s", data);
    //fflush(p.p_stdout);
}

int main()
{
    char buf[5][10]{};
    subprocess_t subprocess = popen2("cat");
    assert(subprocess.cpid != -1);

    // communicate with the subprocess
    // for the test subprocess `cat`, we need a 'n' in each write
    write_data(subprocess, "onen");
    read_data(subprocess, buf[0]);

    write_data(subprocess, "twon");
    write_data(subprocess, "threen");
    read_data(subprocess, buf[1]);
    read_data(subprocess, buf[2]);

    write_data(subprocess, "fourn");
    read_data(subprocess, buf[3]);

    write_data(subprocess, "fiven");
    read_data(subprocess, buf[4]);

    pclose2(&subprocess);
    for (char * s : buf)
        fprintf(stdout, "%sn", s);
    return 0;
}

Final thought:
In popen2.cc, we can incorperate pclose2() to the destructor of
subprocess_t in C++ (given we need to copy the subprocess returned
from popen2(), we may need to add a shared pointer).

class PopenedSubprocess {
    shared_ptr p {}; // shared pointer to the process
public:
    PopenedSubprocess(const char* program) {
        p = make_shared(popen2(program));
    }

    ~PopenedSubprocess() {
        if (p.use_count() == 1) // the last one
            pclose2(p.get());
    }
};

I just made it a repo so that you can run the tests directly. : )

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/862043.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号