Как реализовать последовательные трубы в C? - PullRequest
0 голосов
/ 10 апреля 2020

Я работаю над программой, которая загружает лучший (помеченный как «(лучший)») видеоформат, используя youtube-dl. Он читает аргумент командной строки и запускает дочерний процесс 'youtube-dl -F [url]' . Затем он передает строку с '(best)' подпрограмме, которая извлекает формат и выполняет снова, как дочерний элемент, 'youtube-dl -f [best format] [url]' . Проблема в том, что он работает только для первой ссылки. Возможно, ребенок не пишет в канал должным образом, возможно, родитель не читает из канала. Я потерялся. Спасибо за вашу помощь.

#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] = '\0';
    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;
}

1 Ответ

0 голосов
/ 10 апреля 2020
    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. С помощью этого кода дочерний процесс заполняет буфер канала и затем блокирует ожидание, пока родительский процесс опустошит канал, но родитель никогда не сделает этого, потому что он заблокирован в ожидании выхода дочернего элемента, поэтому вся программа будет заблокирована. (Вы не попадете в тупик для вызовов, когда полный вывод дочернего объекта меньше размера буфера канала. Возможно, поэтому он работает только для первого аргумента командной строки.) Вам необходимо прочитать все данные произведенный ребенком до вы ждете, когда ребенок прекратит свое существование. Для этой программы это так же просто, как перемещение waitpid и его условное выражение ниже while l oop.

Ваша повторная замена дескриптора файла 0 также может привести к нарушению правила C99, которое заканчивается -файл-это липкое состояние. (Это также может объяснить, почему он работает только для первого аргумента командной строки.) Вы могли бы решить эту проблему, вызвав clearerr после dup2, но было бы лучше не связываться с stdin совсем. Вместо этого используйте fdopen, чтобы преобразовать fd[0] в ФАЙЛ.

Соединяя оба этих исправления, ваш родительский код в get_format должен выглядеть примерно так:

} 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 дочерний процесс мог работать асинхронно - то есть, чтобы dl_best возвращал дочерний PID, а не ожидал его сам, а затем get_format ждет обоих дети через некоторое время l oop - но это оптимизация, а не исправление.

...