我正在尝试在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";
答案 0 :(得分:0)
现在当我看到你的整个代码时,它有一些问题。
第一个(而不是真正的问题)是你在调用dup2
之前不必关闭描述符,因为dup2
会为你关闭它们。
第二,是你真的不需要在 fork之前做任何描述符重复。你可以在分叉后做所有这些。说到这一点,当您第一次复制标准进出时,就像在父进程中那样不需要,然后使用这些重复来重置标准进出原始数据。< / p>
您也不会检查某些功能( 您还在 也不需要使用 现在真正的问题可能是:控制台。为了能够处理诸如编辑,光标移动等事情,它是终端仿真器应用程序缓冲输入,并且在用户按 Enter 之前不会将其提供给您的进程dup
,dup2
,fork
和其他功能)的错误。这包括处理EWOULDBLOCK
之类的事情,例如最常返回的read
。O_NONBLOCK
上设置了infd[0]
标记两次,一次在pipe2
来电,另一次在父母手动。fsync
管道和标准输出。它主要用于在慢速设备上打开的文件
系统缓存数据以加速写入调用的位置。虽然控制台输出可以被视为慢速设备,但它仍然没有像磁盘写入那样缓存,并且使用文件描述符写入标准输出是无缓冲的。管道是缓冲的,但仍然没有什么可以同步,因为缓冲区只是一个进程可以在一端写入而另一个进程可以在另一个进程读取。
至于你的程序,编写像你这样的程序的典型方式更像是这样:
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)
无法正常工作的原因是stdin
,stdout
和stderr
没有表征子程序的整个输入和输出。相反,我们对附加到子进程的TTY更感兴趣。实现此类程序的正确方法是使用forkpty()
。