在dup2()之后使用std :: cin从管道读取

时间:2015-02-03 18:00:21

标签: c++ c linux pipe

我只是尝试编写一个简单的loopback函数,该函数在管道设置完成后在子进程中运行,并使用dup2()复制到stdin和stdout。但环回在它试图从管道读取的点处挂起。管道的写端 - 在父进程中 - 是一个使用fputs()的C函数。我知道父进程是有效的,因为如果子回送函数被替换为在C中使用read()的另一个函数,它就可以工作。

一旦我完成了这项工作,我将能够用exec()替换loopback函数,并且我希望能够使用用C ++编写的程序。

有很多类似的问题,但是调用setvbuf()等解决方案对我不起作用(你可以看到我在父回送函数中调用它)。而其他提问者直接在管道文件描述符上使用read()(当我这样做时它会起作用 - 但我想用C ++中的std :: cin来测试它。)

所以主要功能如下: -

int pipeIn[2];  // To be read by child process
int pipeOut[2]; // To be written by child process

#define PARENT_TO_CHILD_READ_END  pipeIn[0]
#define PARENT_TO_CHILD_WRITE_END pipeIn[1]
#define CHILD_TO_PARENT_READ_END  pipeOut[0]
#define CHILD_TO_PARENT_WRITE_END pipeOut[1]

int main(int argc, char** argv) {
    pipe(pipeIn);
    pipe(pipeOut);

    pid_t hijo = fork();

    if (hijo == 0) {
        // CHILD
        dup2(PARENT_TO_CHILD_READ_END, STDIN_FILENO);
        dup2(CHILD_TO_PARENT_WRITE_END, STDOUT_FILENO);
        close(PARENT_TO_CHILD_READ_END);
        close(CHILD_TO_PARENT_WRITE_END);
        close(PARENT_TO_CHILD_WRITE_END);
        close(CHILD_TO_PARENT_READ_END);

        Child_plusplus_Loopback();

    } else if (hijo == -1) {
        perror("fork");
        exit(EXIT_FAILURE);

    } else {
        // PARENT
        close(PARENT_TO_CHILD_READ_END);
        close(CHILD_TO_PARENT_WRITE_END);

        Parent_FILE_Loopback(
                PARENT_TO_CHILD_WRITE_END,
                CHILD_TO_PARENT_READ_END);

        close(PARENT_TO_CHILD_WRITE_END);
        close(CHILD_TO_PARENT_READ_END);
        wait(NULL);
    }

    return 0;
}

环回功能如下所示: -

void
Parent_FILE_Loopback(const int outPipe, const int inPipe) {
    FILE * toChild   = fdopen(outPipe, "w");
    FILE * fromChild = fdopen(inPipe, "r");
    setvbuf(toChild, NULL, _IONBF, 0);

    fputs("Hello", toChild);

    const size_t bufferSize(256);
    char         buffer[ bufferSize ];

    fgets(buffer, bufferSize, fromChild);

    printf("PARENT : %s\n\n", buffer);
}

void
Child_plusplus_Loopback(void) {
    string buffer;

    cin >> buffer; // this hangs

    string message("CHILD : ");
    message += buffer;

    cout << message;
}

strace -f的输出如下所示: -

clone(Process 6989 attached (waiting for parent)
Process 6989 resumed (parent 6988 ready)
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb74e5768) = 6989
[pid  6989] dup2(3, 0 <unfinished ...>
[pid  6988] close(3)                    = 0
[pid  6989] <... dup2 resumed> )        = 0
[pid  6988] close(6 <unfinished ...>
[pid  6989] dup2(6, 1 <unfinished ...>
[pid  6988] <... close resumed> )       = 0
[pid  6989] <... dup2 resumed> )        = 1
[pid  6988] fcntl64(4, F_GETFL <unfinished ...>
[pid  6989] close(3 <unfinished ...>
[pid  6988] <... fcntl64 resumed> )     = 0x1 (flags O_WRONLY)
[pid  6989] <... close resumed> )       = 0
[pid  6989] close(6 <unfinished ...>
[pid  6988] brk(0 <unfinished ...>
[pid  6989] <... close resumed> )       = 0
[pid  6988] <... brk resumed> )         = 0x848f000
[pid  6989] close(4 <unfinished ...>
[pid  6988] brk(0x84b0000 <unfinished ...>
[pid  6989] <... close resumed> )       = 0
[pid  6988] <... brk resumed> )         = 0x84b0000
[pid  6989] close(5)                    = 0
[pid  6988] fstat64(4,  <unfinished ...>
[pid  6989] fstat64(0,  <unfinished ...>
[pid  6988] <... fstat64 resumed> {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
[pid  6989] <... fstat64 resumed> {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
[pid  6988] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...>
[pid  6989] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...>
[pid  6988] <... mmap2 resumed> )       = 0xb77d3000
[pid  6989] <... mmap2 resumed> )       = 0xb77d3000
[pid  6988] _llseek(4, 0,  <unfinished ...>
[pid  6989] read(0,  <unfinished ...>
[pid  6988] <... _llseek resumed> 0xbfefde40, SEEK_CUR) = -1 ESPIPE (Illegal seek)
[pid  6988] fcntl64(5, F_GETFL)         = 0 (flags O_RDONLY)
[pid  6988] fstat64(5, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
[pid  6988] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77d2000
[pid  6988] _llseek(5, 0, 0xbfefde40, SEEK_CUR) = -1 ESPIPE (Illegal seek)
[pid  6988] munmap(0xb77d3000, 4096)    = 0
[pid  6988] write(4, "Hello", 5)        = 5
[pid  6989] <... read resumed> "Hello", 4096) = 5
[pid  6988] read(5,  <unfinished ...>
[pid  6989] brk(0)                      = 0x848f000
[pid  6989] brk(0x84b0000)              = 0x84b0000
[pid  6989] read(0, 0xb77d3000, 4096)   = ? ERESTARTSYS (To be restarted)
[pid  6988] <... read resumed> 0xb77d2000, 4096) = ? ERESTARTSYS (To be restarted)
[pid  6989] --- SIGWINCH (Window changed) @ 0 (0) ---
[pid  6988] --- SIGWINCH (Window changed) @ 0 (0) ---
[pid  6989] read(0,  <unfinished ...>
[pid  6988] read(5, 

3 个答案:

答案 0 :(得分:0)

std::cin(或FILE*)的任何读取通常都会被缓冲, 这意味着对read()的调用会请求大量的。{1}} 字节。系统安排从控制台读取甚至返回 如果读取的字节数较少,但这并不适用于从a读取 管;调用填充缓冲区的read只会返回 填充缓冲区或管道的写入侧已关闭。

您可以通过调用std::cin.setbuf来稍微控制缓冲, 但这可能很棘手;通常,它应该在第一个之前完成 来自std::cin的输入。如果您使用>>输入字符串, std::cin会继续致电read,直到看到空白区域(或{。}} 文件结束)。

编辑:

你还不太清楚自己要做什么。如果你需要一个 基于消息的协议,那么你需要的不仅仅是管道 和iostream;这两者都是面向流的,而不是面向消息的。

我过去常常通过编写长度来处理这个问题 消息(比如四字节整数),然后是消息。这个 如果管道上只有一个读卡器和一个写入器,则相当简单; 有了更多的作家,你必须确保每次写长度加 消息是原子的,如果有几个消息,它就无法真正完成 读者 - 你需要为每个读者提供一个单独的管道。对于文本 格式化,您可以使用std::ostringstreamstd::istringstream; 写入,将std::ostringstream中的数据转换为字符串,然后 然后:

void
writeOneMessage( int fd, std::string const& message )
{
    std::size_t size = message.size();
    char sizeBuffer[4] = 
    {
        (size >> 24) & 0xFF,
        (size >> 16) & 0xFF,
        (size >>  8) & 0xFF,
        (size      ) & 0xFF
    }
    write( fd, sizeBuffer, 4 );
    write( fd, message.data(), message.size() );
}

阅读要复杂得多,因为有很多 您必须检查的其他错误情况:

std::string
readOneMessage( int fd )
{
    char sizeBuffer[4];
    if ( read( fd, sizeBuffer, 4 ) != 4 ) {
        //  Really too simple: if you read 0, it's end of file
        //  if you read anythong other than 0 or 4, it's a serious
        //  error.
    }
    size_t size = ((sizeBuffer[0] & 0xFF) << 24)
                | ((sizeBuffer[1] & 0xFF) << 16)
                | ((sizeBuffer[2] & 0xFF) <<  8)
                | ((sizeBuffer[3] & 0xFF)      );
    std::string message( size );
    if ( read( fd, &message[0], size ) != size ) {
        //  Can only be a format error...
    }
    return message;
}

同样,一旦您阅读了该消息,您就可以使用它来构建一个消息 std::istringstream,并按照您的意愿解析它。

这是您可靠地实现基于消息的唯一方法 带管道的协议;另一种方法是写一个'\0' 每个消息的终止字符串,并逐字节读取,直到找到 '\0'。 (实际上,使用FILE*,将所有流设置为 行缓冲,通常会在大多数情况下工作,提供消息 足够小,但它不是真的有保证,也不可靠。)

答案 1 :(得分:0)

std::cin读取通常会缓冲。这意味着>>运算符在读取换行符或到达流末尾之前不会返回。即使std::cin在您的情况下没有缓冲,流也必须继续尝试读取,直到它看到字符串的结尾,这发生在空格或流的末尾(不是必然在当前可用字节的末尾)。无论如何,您无需担心基础read()调用的详细信息。这是您的C ++库实现的责任。

您的服务器写入&#34; Hello&#34;的五个字符。到管道,但fputs()不会自动跟他们使用换行符(不像puts())或其他任何内容。如果你想要发送换行符 - 在读取端与线缓冲很好地互操作 - 那么你必须明确地发送它:

fputs("Hello\n", toChild);

fputs("Hello", toChild);
fputc('\n', toChild);

即使读取方是无缓冲的,您也需要至少发送一个空格或制表符,以便读者能够识别字符串的结尾。只要你还需要这样做,你最好使用换行符。

任何方式,如果输出流被缓冲,那么你可能想用fflush(toChild)来跟进它,但是因为你明确地使它无缓冲(不一定是明智的选择),上面应该是足以使客户的阅读回归。

请注意,类似的考虑因素适用于孩子发送回父母的邮件:fgets()读取换行符或EOF,并且看起来孩子不会终止其回复带换行符的消息。或冲洗。

答案 2 :(得分:0)

正确设置文件描述符后,您需要一个已定义的通信协议。

使用std :: cout / std :: cin:

的管道
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/wait.h>


int parent_pipe[2];
int child_pipe[2];

void parent_loop() {
    std::cout << "Hello Child" << ' ' << "Disconnect" << std::endl;
    std::string receive("Parent Receives: ");
    while(true) {
        std::string buffer;
        if( ! (std::cin >> buffer) || buffer == "Disconnect")
            break;
        receive += buffer;
    }
    std::cout << "Disconnect";
    std::cerr << receive << std::endl;
}

void child_loop() {
    std::string receive(" Child Receives: ");
    while( true) {
        std::string buffer;
        if( ! (std::cin >> buffer) || buffer == "Disconnect")
            break;
        receive += buffer;
    }
    std::cout << "Hello Parent" << std::endl;
    std::cout << "Disconnect" << std::endl;
    std::cerr << receive << std::endl;
}


int main(int argc, char** argv) {
    pipe(parent_pipe);
    pipe(child_pipe);

    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        return EXIT_FAILURE;
    }
    else if (pid) {
        // Parent
        if((dup2(child_pipe[0], STDIN_FILENO) == -1)
        || (dup2(parent_pipe[1], STDOUT_FILENO) == -1))
        {
            std::cerr << "Setup Failure\n";
            return EXIT_FAILURE;
        }
        close(child_pipe[1]);
        close(parent_pipe[0]);

        parent_loop();

        close(child_pipe[0]);
        close(parent_pipe[1]);
    }
    else {
        // Child
        if((dup2(parent_pipe[0], STDIN_FILENO) == -1)
        || (dup2(child_pipe[1], STDOUT_FILENO) == -1))
        {
            std::cerr << "Setup Failure\n";
            return EXIT_FAILURE;
        }
        close(parent_pipe[1]);
        close(child_pipe[0]);

        child_loop();

        close(parent_pipe[0]);
        close(child_pipe[1]);
    }
    return 0;
}

请注意使用std::endl将分隔符放入流中并刷新流。 如果您想要未格式化的IO,则可以使用读写函数(孩子可能会读取父级写入的块直到EOF)。 注意:记录和错误消息转到std :: cerr(STDERR_FILENO)

输出:

 Child Receives: HelloChild
Parent Receives: HelloParent