如何在不等待换行的情况下从流中读取?

时间:2013-07-30 16:50:05

标签: c console posix pipe

我正在尝试在C中编写一个简单的控制台程序,它会分叉,运行子进程,并使用pipe()读取父程序的所有stdin并将其发送到子程序的stdin并读取所有stdout子程序的一部分,并将其发送到父程序的stdout。最后,我可以让父母做一些事情而不仅仅是传递这些数据 - 现在,如果父程序的行为好像是直接运行子程序就足够了。

似乎工作正常,除了当我从孩子的输出传输的流中读取()时,在遇到换行符之前它不会返回任何内容。这意味着如果子进程在一行中间询问问题,则问题将永远不会出现,直到用户键入答案后才会出现,这是不可接受的。

我对父母的标准输入也有同样的问题。即使用户应该能够用简单的“y”回答问题,程序也无法在用户按下输入之前读取“y”,这也是不可接受的。

我将输入流设置为非阻塞:

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

它工作正常,但read()返回-1,直到遇到换行符。

我能做些什么来从流中读取实际数据而不受部分线路的“保护”?或者我应该采取一些完全不同的方法?是否有一些开源程序可以执行类似我可以检查的内容?

代码在这里:

#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();
        }
    }
}

改编自this blog post

test.php(用作子进程)是这样的:

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

3 个答案:

答案 0 :(得分:0)

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

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

第二,是你真的不需要在 fork之前做任何描述符重复。你可以在分叉后做所有这些。说到这一点,当您第一次复制标准进出时,就像在父进程中那样不需要,然后使用这些重复来重置标准进出原始数据。< / p>

您也不会检查某些功能(dupdup2fork和其他功能)的错误。这包括处理EWOULDBLOCK之类的事情,例如最常返回的read

您还在O_NONBLOCK上设置了infd[0]标记两次,一次在pipe2来电,另一次在父母手动。

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

现在真正的问题可能是:控制台。为了能够处理诸如编辑,光标移动等事情,它是终端仿真器应用程序缓冲输入,并且在用户按 Enter 关键。您必须使用termios功能来禁用它。有关如何禁用CANONICAL模式(禁用行缓冲模式)的一个非常简单的示例,请参阅for example here


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

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]` */
}

我会想到的代码越来越简单。

答案 1 :(得分:0)

似乎我需要获取流0和1的状态标志(带有fcntl),并在管道端点上设置相同的标志,用于代替实际流0和1。{来自PHP的{1}}提示仍然​​没有显示,但并不是它在用户输入内容之前不显示,而是它根本不显示,这很奇怪。但我的小程序与ffmpeg(如果你想要覆盖文件会提示你)一起工作得非常好,所以我认为这对我的目的来说已经足够了。感谢所有回答的人。

答案 2 :(得分:0)

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