1

I'm trying to write a simple console program in C that forks, runs a child process and, with pipe(), reads all stdin of the parent program and sends it to the stdin of the child program and reads all stdout of the child program and sends it to the stdout of the parent program. Eventually, I can make the parent do something other than just passing this data through—for now, it would suffice if the parent program behaves as if the child program were run directly.

Seems to work fine, except that when I read() from the stream that is piped from the child's output it won't return anything until a newline is encountered. This means that if the child process asks a question in middle of a line, the question will never appear until after the user types the answer, which is unacceptable.

I also have the same problem with the parent's stdin. Even if the user should be able to answer the question with a simple "y", the program cannot read the "y" until the user pushes enter, which is also unacceptable.

I'm setting the input stream to nonblocking with:

fcntl(stream, F_SETFL, fcntl(stream, F_GETFL) | O_NONBLOCK);

It works fine, but read() returns -1 until a newline is encountered.

Is there something I can do to read the actual data from the streams without being "protected" from partial lines? Or is there some totally different approach I should take? Is there some open source program that does something similar that I can examine?

Code is here:

#include <unistd.h>
#include <sched.h>
#include <fcntl.h>

int main(int argc, const char * const argv[])
{
    int outfd[2];
    int infd[2];

    int oldstdin, oldstdout;

    pipe2(outfd, O_NONBLOCK); // Where the parent is going to write to
    pipe2(infd, O_NONBLOCK); // From where parent is going to read

    oldstdin = dup(0); // Save current stdin
    oldstdout = dup(1); // Save stdout

    close(0);
    close(1);

    dup2(outfd[0], 0); // Make the read end of outfd pipe as stdin
    dup2(infd[1],1); // Make the write end of infd as stdout

    if(!fork())
    {
        const char * pChildArguments[] = { "/usr/bin/php", "test.php", 0 };
        close(outfd[0]); // Not required for the child
        close(outfd[1]);
        close(infd[0]);
        close(infd[1]);

        execv(pChildArguments[0], (char * const *)pChildArguments);
    }
    else
    {
        char input[100];
        close(0); // Restore the original std fds of parent
        close(1);
        dup2(oldstdin, 0);
        dup2(oldstdout, 1);

        close(outfd[0]); // These are being used by the child
        close(infd[1]);

        fcntl(infd[0], F_SETFL, fcntl(infd[0], F_GETFL) | O_NONBLOCK);
        fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);

        for (;;) {
            ssize_t readReturnValue;

            readReturnValue = read(infd[0], input, 100);
            if (readReturnValue == 0) { break; }
            if (readReturnValue > 0) {
                write(1, input, readReturnValue);
                fsync(1);
            }
            readReturnValue = read(0, input, 100);
            if (readReturnValue > 0) {
                write(outfd[1], input, readReturnValue);
                fsync(outfd[1]);
            }
            sched_yield();
        }
    }
}

It was adapted from this blog post.

The test.php (used as a child process) is this:

<?php
echo "This lines goes through.\n";
$a = readline("Say something: ");
echo "You said " . $a . "\n";
4

3 回答 3

0

看来我需要获取fcntl流 0 和 1 的状态标志(带有 ),并在管道端点上设置相同的标志,以代替真正的流 0 和 1。PHP 的readline()提示仍然没有出现,但这并不是说它在用户输入某些内容之前不显示,而是它根本不显示,这很奇怪。但是我的小程序与 ffmpeg 配合得很好(它会提示你是否要覆盖文件),所以我认为它对我的目的来说已经足够了。我感谢所有回答的人。

于 2013-07-31T01:15:19.767 回答
0

现在,当我看到您的整个代码时,它存在一些问题。

第一个(并不是真正的问题)是您不必在调用之前关闭描述符dup2,因为它dup2会为您关闭它们。

第二,你真的不需要在分叉之前做任何描述符复制。你可以在分叉之后做所有这些。说到这一点,当您第一次进出标准时,不需要这样做,因为在父流程中,您可以使用这些副本将标准输入和输出重置回原件。

您也不会检查某些函数(dupdup2fork其他函数)的错误。这包括EWOULDBLOCKread最常返回的处理一样的事情。

您还设置了两次O_NONBLOCK标志infd[0],一次在pipe2调用中,另一次在父项中手动设置。

fsync也不需要使用管道和标准输出。它主要用于在慢速设备上打开的文件,系统缓存数据以加快写入调用。虽然控制台输出可以被认为是一个慢速设备,但它仍然没有像磁盘写入那样被缓存,并且使用文件描述符写入标准输出是无缓冲的。管道是缓冲的,但仍然没有什么要同步的,因为缓冲区只是为了让一个进程可以在一端写入,而另一个进程可以在另一端读取。

现在真正的问题可能是:控制台。为了能够处理编辑、光标移动等事情,终端仿真器应用程序Enter会缓冲输入,并且在用户按下键之前不会将其提供给您的进程。您必须使用termios功能来禁用它。有关如何禁用 CANONICAL 模式(禁用行缓冲模式)的非常简单的示例,请参见此处的示例


至于您的程序,编写像您这样的程序的典型方式更像是这样的:

int pipes1[2], pipes2[2];

pipe(pipes1);
pipe(pipes2);

int res = fork();
if (res == -1)
    perror("fork");
else if (res == 0)
{
    dup2(pipes1[0], STDIN_FILENO);
    dup2(pipes2[1], STDOUT_FILENO);

    close(pipes1[1]);
    close(pipes2[0]);

    exec(...);
}
else
{
    close(pipes1[0]);
    close(pipes2[1]);

    /* Read from `pipes2[0]`, write to `pipes1[1]` */
}

我认为代码更少更简单。

于 2013-07-30T21:41:03.027 回答
0

没有任何东西正常工作的原因是stdinstdout并且stderr不表征子程序的整个输入和输出。相反,我们对附加到子进程的 TTY 更感兴趣。实现此类程序的正确方法是使用forkpty().

于 2013-10-24T20:31:43.913 回答