繁体   English   中英

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

[英]Using std::cin to read from pipe after dup2()

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

一旦完成这项工作,我就可以用exec()替换回送功能,并且我希望它可以与用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, 

std::cin (或FILE* )进行的任何读取通常都会被缓冲,这意味着对read()的调用将请求大量字节。 系统安排从控制台进行的读取返回,即使已读取较少的字节也是如此,但这并不适用于从管道进行的读取。 仅当缓冲区已填充或管道的写侧已关闭时,才会调用调用填充缓冲区的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* ,将所有流设置为行缓冲,通常会在大多数时间工作,前提是消息足够小,但并不能保证也不可靠。)

std::cin读取通常会被缓冲。 这意味着>>运算符只有在读取换行符或到达流的末尾后才返回。 即使std::cin发生不以你的情况进行缓冲,流必须不断尝试读取,直到它看到字符串的结束,这发生在空白或流的末尾( 不一定在目前结束-可用字节)。 无论如何,您都不必担心底层read()调用的详细信息。 这是您的C ++库实现的责任。

您的服务器将“ Hello”的五个字符写入管道,但是fputs()不会自动在它们后跟换行符(不同于puts() )或其他任何字符。 如果要发送换行符-以便与读取端的行缓冲良好地互操作-那么必须显式发送:

fputs("Hello\n", toChild);

要么

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

即使读取端没有缓冲,您也需要至少发送一个空格或制表符,以便读取器可以识别字符串的结尾。 只要您仍然需要这样做,最好使用换行符。

无论如何,如果输出流已缓冲,那么您可能需要使用fflush(toChild)进行后续处理,但是由于您明确地使其fflush(toChild)缓冲(不一定是明智的选择),因此上述内容足以使客户的阅读回报。

请注意,类似的考虑也适用于子级发回父级的消息: fgets()读取直到换行符或EOF,并且看起来子级不以换行符终止其答复消息。 或冲洗。

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

使用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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM