如何在 C 中实现连续的管道?
How to implement successive pipes in C?
我正在开发一个使用 youtube-dl 下载最佳(标记为“(best)”)视频格式的程序。它读取一个 command-line 参数,然后启动一个 child 进程 'youtube-dl -F [url]'。然后它将带有“(best)”的行传递给提取格式并执行的例程,再次作为 child、'youtube-dl -f [best format] [url]'。问题是它仅适用于第一个 link。也许 child 没有正确写入管道,也许 parent 没有从管道读取。我迷路了。感谢您的帮助。
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define LINE_LEN 255
enum { ERROR=-1, CHILD };
void error(char *msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(1);
}
void dl_best(char *format, char *url)
{
char fmt[4];
int status;
pid_t pid;
for (int i = 0; *format != ' '; format++, i++)
fmt[i] = *format;
fmt[3] = '[=10=]';
switch(pid = fork()) {
case ERROR:
error("Failed to pipe in dl_best");
break;
case CHILD:
if (execlp("youtube-dl", "youtube-dl", "-f", fmt, url, NULL) == -1)
error("Failed to execle() in dl_best");
break;
default:
if (waitpid(pid, &status, 0) == -1)
error("Waitpid failed in dl_best()");
break;
}
}
void get_format(char *url)
{
pid_t pid;
int fd[2], status;
char line[LINE_LEN];
if (pipe(fd) == -1)
error("Pipe failed");
if ((pid = fork()) == ERROR) {
error("Failed to create a child precess in get_format()");
} else if (pid == CHILD) {
if (close(fd[0]) == -1)
error("Child failed to close reading pipe");
if (dup2(fd[1],1) == -1)
error("Dup2 failed in get_format()");
if (execlp("youtube-dl", "youtube-dl", "-F", url, NULL) == -1)
error("Failed to execute get_formats");
} else { //parent
if (close(fd[1]) == -1)
error("Parent failed to close writing pipe");
if (dup2(fd[0],0) == -1)
error("Dup2 failed in get_format()");
if (waitpid(pid, &status, 0) == -1)
error("Waitpid failed in get_format()");
while (fgets(line, LINE_LEN, stdin)) {
if (strstr(line, "(best)") != NULL)
dl_best(line, url);
}
if (close(fd[0]) == -1)
error("Parent failed to close reading pipe");
}
}
int main(int argc, char *argv[])
{
//int fd[2], status, argc_cp = argc;
//dl_best("22 ", argv[--argc]);
while (--argc)
get_format(argv[argc]);
return 0;
}
if (dup2(fd[0],0) == -1)
error("Dup2 failed in get_format()");
if (waitpid(pid, &status, 0) == -1)
error("Waitpid failed in get_format()");
while (fgets(line, LINE_LEN, stdin)) {
if (strstr(line, "(best)") != NULL)
dl_best(line, url);
}
这是在 C 中从 child 管道传输到 parent 时的常见错误。使用此代码,child 将填满管道缓冲区,然后阻塞等待 parent 来排空管道,但是 parent 永远不会这样做,因为它在等待 child 退出时被阻塞,所以整个程序将死锁。
(在 child 的完整输出小于管道缓冲区大小的情况下,您不会遇到调用死锁。这可能就是它似乎仅适用于第一个命令行参数的原因。)您需要要读取 child 在 之前生成的所有数据,您需要等待 child 终止。对于这个程序,就像将 waitpid
及其条件移动到 while
循环下方一样简单。
您重复替换文件描述符 0 也可能 运行违反 C99 规则,即 end-of-file 是粘性条件。 (这也可以解释为什么它似乎只适用于第一个命令行参数。)您 可以 通过在 dup2
之后调用 clearerr
来解决这个问题,但是它最好不要乱用标准输入。相反,使用 fdopen
将 fd[0]
转换为文件。
将这两个修复放在一起,您在 get_format 中的 parent-side 代码应该如下所示:
} else { //parent
if (close(fd[1]) != 0)
error("Parent failed to close writing pipe");
FILE *fp = fdopen(fd[0], "rt");
if (!fp)
error("Parent failed to allocate a FILE");
while (fgets(line, LINE_LEN, fp)) {
if (strstr(line, "(best)") != NULL)
dl_best(line, url);
}
if (fclose(fp) != 0)
error("Parent failed to close reading pipe");
if (waitpid(pid, &status, 0) != pid)
error("Waitpid failed in get_format()");
}
(注意 fd[0]
和 fp
一起被 fclose
关闭;这不是必须的(事实上,这将是 错误的 ) 在其上调用 close
。)
我也会考虑允许 dl_best
child 异步地 运行 - 也就是说,dl_best
return child PID而不是等待它本身,然后 get_format
在其 while 循环之后等待 both children - 但这是优化,而不是错误修复。