通过管道(C)写入外部程序
Posted
技术标签:
【中文标题】通过管道(C)写入外部程序【英文标题】:writing to external program through pipe (C) 【发布时间】:2020-01-05 16:34:15 【问题描述】:我想使用外部程序来处理内存中的数据。像外部压缩器,编码器,任何处理我的数据并得到结果的东西。我读了很多关于管道的文章,但它仍然不起作用。所以我最终得到了一个简单的程序,它试图通过这样的管道写入外部程序,让它打印到标准输出:
stdout
(w) pipeA (r) $prog +---+
+-----------+ /~~~~~~~~~~~\ |1|
|[1] [0]| ----> |0 1| ----> | |
+~~> +-----------+ \~~~~~~~~~~~/ | |
| +---+
|
+-+
write() |
+-+
我还是一无所获。 我的代码是这样的:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
int pipA[2];
int pid;
char buf_IN[32] = "Hello pipe!\n";
ssize_t n_written;
if ((pipe(pipA) == -1))
perror("pipe failed");
exit(1);
if ((pid = fork()) < 0)
perror("fork failed");
exit(2);
/*****************************/
if (pid == 0)
/* in child */
dup2(0, pipA[0]); // pipA[read(0)-end]->$prog[write0-end]
close(pipA[1]); // $prog won't write to this pipe(A)
// external ``$prog''ram
execlp("wc", "wc", (char *) 0); // out should be: ' 1 2 12'
//execlp("base64", "base64", (char *) 0); // out should be: 'SGVsbG8gcGlwZSEK'
;///if we're here something went wrong
perror("execlp() @child failed");
exit(3);
else
/* in parent */
//dup2(pipA[1], 0); // STDIN -> pipA // that supposed to connect STDIN->pipA; just in case I needed it
close(pipA[0]); // we won't read it, let $prog write to stdout
//perror("execlp() @parent failed");
//exit(4);
n_written = write(pipA[1], buf_IN, strlen(buf_IN));
close(pipA[1]); // I guess this will close the pipe and send child EOF
// base64: read error: Input/output error
// wc: 'standard input': Input/output error
// 0 0 0
return 0;
评论显示我在做什么。我不得不承认我没有在管道中得到这些 dup(),这就是我认为在这里造成问题但不知道的原因。 你能帮忙解决这个看似简单的问题吗?任何帮助表示赞赏。
【问题讨论】:
【参考方案1】:诊断
你有 dup2()
的参数从后到前。你需要:
dup2(pipA[0], 0);
关闭文件描述符
您没有在子项中关闭足够多的文件描述符:
经验法则:如果您
dup2()
管道的一端到标准输入或标准输出,关闭两者
返回的原始文件描述符
pipe()
尽快地。
特别是,您应该在使用任何
exec*()
函数族。
如果您使用以下任一方式复制描述符,该规则也适用
dup()
或者
fcntl()
F_DUPFD
处方
您的代码中也有一些未使用的定义和未使用的变量。去掉你所有的 cmets(但我有几个解释发生了什么),并进行了适当的修复,我最终得到:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
int pipA[2];
int pid;
char buf_IN[32] = "Hello pipe!\n";
ssize_t n_written;
if ((pipe(pipA) == -1))
perror("pipe failed");
exit(1);
if ((pid = fork()) < 0)
perror("fork failed");
exit(2);
if (pid == 0)
/* Child: Connect pipA[0] (read) to standard input 0 */
dup2(pipA[0], 0);
close(pipA[1]); /* Close write end of pipe */
close(pipA[0]); /* Close read end of pipe */
execlp("wc", "wc", (char *)0);
perror("execlp() @child failed");
exit(3);
else
close(pipA[0]); /* Close read end of pipe */
n_written = write(pipA[1], buf_IN, strlen(buf_IN));
if (n_written != (ssize_t)strlen(buf_IN))
perror("short write");
exit(4);
close(pipA[1]); /* Close write end of pipe — EOF for child */
/* Optionally wait for child to die before exiting */
// #include <sys/wait.h> // With other #include lines
// int corpse;
// int status;
// while ((corpse = wait(&status)) > 0)
// printf("Child %d exited with status 0x%.4X\n", corpse, status);
return 0;
运行时,会产生:
1 2 12
看起来不错。
如果没有 wait()
循环,您可能会在 shell 提示后看到来自 wc
的输出(因此看起来好像程序正在等待您的输入,但实际上,它将是等待输入的外壳);使用等待循环,您将正确分离 shell 提示符的输出。您不必在循环体中打印任何内容,但这样做会让人放心。
【讨论】:
谢谢,很快。我将尝试消化它并获得 dup2() 。 PS。我已经编辑了未使用的变量 PPS。刚刚测试了您的程序,它仍在等待wc
的输出,而无需任何用户交互。指出我写错了dup2()
的一件事是wc
没有终止。
它确实给出了答案,但程序仍在等待用户输入。 - 顺便提一句。你能评论那些 dup2()s 和 close()s 让人们知道他们在做什么吗?我问的原因是我显然不理解 dup2() 并且仍然有疑问。我是否正确关闭 pipA[] 以父母结尾?说 dup2(pipA[0], 0);
连接 pipA[0]--->0 对吗? pipA[0] 会变成 0 还是 0 会变成 pipA[0]?
我在答案中添加了一些注释(并注释掉了代码)。当孩子调用dup2(pipA[0], 0)
,然后在调用完成后,pipA[0]
和0
引用与调用之前pipA[0]
相同的打开文件描述(即使它们是两个不同的打开文件描述符——小心使用“描述”与“描述”)。当您随后关闭pipA[0]
时,只有标准输入指的是管道的读取端(这是您想要的)。请注意,父进程还有一个打开的文件描述符,它引用相同的打开文件描述——直到它关闭pipA[0]
。
谢谢,这有助于我更好地理解它。【参考方案2】:
为了不重复,也不在不熟悉的领域扮演专家,我将其发布为答案。
我完成了任务
(w) pipeA (r) child (w) pipeB (r)
+-----------+ /~~~~~~~~~~~~~\ +-----------+
+~~~>|[1] [0]| ----> |0 $prog 1| ----> |[1] [0]| ~~~+
| +-----------+ \~~~~~~~~~~~~~/ +-----------+ |
| \/
+-+ +--+
write() | | read()
+-+ +--+
dup(pA[0],0) dup(pB[1],1)
close(pA[1]) close(pA[0])
close(pA[0]) close(pA[1])
带有可以读取的第二个管道。一切以此类推。如果有什么重大问题或者我应该注意的事情,请说出来。 (对不起python风格的缩进,希望你不介意)
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte);
int main(void)
int pipA[2], pipB[2];
int pid;
char buf_IN[32] = "Hello pipe!\n";
char buf_OUT[1024];
char *bptr;
ssize_t n_written, n_read = 0, a_read = 0, to_read = sizeof(buf_OUT);
if ((pipe(pipA) == -1) || (pipe(pipB) == -1))
perror("pipe failed");
exit(1);
if ((pid = fork()) < 0)
perror("fork failed");
exit(2);
if (pid == 0)
/* in child */
dup2(pipA[0], 0); // connect pipe A (read end/exit) to stdin (write end/input)
close(pipA[1]); // close unused pipe A end
close(pipA[0]); // close - " - //
;
dup2(pipB[1], 1); // connect stdout (read end/output) to pipe B (write end/entry)
close(pipB[0]); // close unused pipe B ends
close(pipB[1]); // close - " - //
execlp("lzip", "lzip", "-c", (char *)0);
;
perror("execlp() @child failed");
exit(3);
else
/* in parent */
close(pipA[0]); // close pipe A read end - will only write to this one
n_written = write(pipA[1], buf_IN, strlen(buf_IN));
if (n_written < 0)
perror("error: read_whole_file(pipA[1], ...) failed miserably\n");
close(pipA[1]); // close write end which subsequently signals EOF to child
;
close(pipB[1]); // close pipe B write end - will only read form this one
a_read = read_whole_file(pipB[0], buf_OUT, sizeof(buf_OUT));
if (a_read < 0)
perror("error: read_whole_file(pipB[0], ...) failed miserably\n");
close(pipB[0]); // close read end after reading
;
write(STDOUT_FILENO, buf_OUT, a_read); // dump it to parent's stdout - equivalent of processing data received from external program/plugin
return 0;
ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte) // read whole file
ssize_t n_read, a_read = 0, to_read = nbyte;
char *bptr = (char*)buf;
size_t BUF_SIZE = 4096;
do
if(to_read < BUF_SIZE)
BUF_SIZE = to_read;
n_read = read(fildes, bptr, BUF_SIZE);
if (n_read < 0) // recover from temporarily failed read (got it from git wrapper)
if ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK))
continue;
bptr += n_read;
to_read -= n_read;
a_read += n_read;
while ((n_read>0) && (to_read>0));
;
if (n_read < 0)
a_read = n_read;
return a_read;
要注意不是那么明显 - 我仍然没有得到 close(pipA[0]);
和 close(pipB[1]);
的孩子。为什么它们不再使用了?
另外dup2(pipB[1], 1);
,我认为它会是其他方式,但它没有工作,所以通过试用结束错误我来了。
【讨论】:
在这种情况下,您可能会在不关闭子节点的所有管道端的情况下逃脱,但在其他情况下(例如,3 个进程和 2 个管道,或更多,或多个管道在两个方向工作)关闭管道末端可能会导致进程在需要时无法获得 EOF,因为无意写入管道的进程仍将其打开以进行写入。或者一个进程可能会挂起,因为有一个进程打开了管道以供读取,因此系统假设它可能会从中读取,即使它无意这样做,所以它没有向写入者发出信号,而是阻止它。以上是关于通过管道(C)写入外部程序的主要内容,如果未能解决你的问题,请参考以下文章
使用 Java 通过 Windows 上的外部应用程序管道数据
乐鑫esp8266学习rtos3.0笔记:分享在 esp8266 C SDK如何通过外部写入参数,程序里面实现动态获取参数。