Использовать два канала в C или один канал для более чем двух операций чтения / записи?И как? - PullRequest
0 голосов
/ 28 октября 2018

У меня есть следующий упрощенный шаблон кода:

pid_t pid;
int pipe1[2], pipe2[2];
pid = fork();
pipe(pipe1); pipe(pipe2)
if(pid == 0)  //child
{
    read(pipe1[0],...);
    write(pipe1[1],...);
    close(pipe1[0]);
    close(pipe1[1]);

    close(pipe2[1]);
    read(pipe2[0]...);
}
else //parent
{
    write(pipe1[1],...);
    wait(NULL);
    read(pipe1[0]...);
    close(pipe1[0]);
    close(pipe1[1]);

    close(pipe2[0]);
    write(pipe2[1]...);
}

Если я не использую pipe2 в родительском и дочернем элементах, код работает отлично, но если я это сделаю, кажется, что дочернему элементу нечегочитать (программа ничего не делает, пока я не нарушу ее).Кроме того, есть ли способ использовать только один канал для более чем 2 чтения / записи?Я пытался использовать wait (NULL) более одного раза, но это не сработало.

Ответы [ 2 ]

0 голосов
/ 28 октября 2018

у вас есть 2 ошибки в вашем коде.

  1. вы создали мертвую блокировку с помощью оператора wait.ваши родители ждут ребенка, пока ребенок ждет, пока родители что-то напишут.
  2. вы сделали fork до того, как сделали трубку.следовательно, ваш ребенок и ваш родитель видят разные версии неподключенных каналов.

Вот фиксированная версия вашей программы:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>

int main() {
  pid_t pid;
  int pipe1[2], pipe2[2];
  char *m1 = "hello";
  char *m2 = "world";
  char *m3 = "bye";


  pipe(pipe1);
  pipe(pipe2);

  pid = fork();

  if(pid == 0) {  //chil
    char buf1[256], buf2[256];
    int len;
    len = read(pipe1[0], buf1, 255);
    buf1[len] = 0;
    write(pipe1[1], m2, strlen(m2));

    close(pipe1[0]);
    close(pipe1[1]);

    close(pipe2[1]);
    len = read(pipe2[0], buf2, 255);
    buf2[len] = 0;

    printf("child read %s %s\n", buf1, buf2);

  }
  else {
    char buf[256];
    int len;

    write(pipe1[1], m1, strlen(m1));
    //wait(NULL);
    len = read(pipe1[0], buf, 255);
    buf[len] = 0;

    close(pipe1[0]);
    close(pipe1[1]);

    close(pipe2[0]);
    write(pipe2[1], m3, strlen(m3));

    wait(NULL);
    printf("Parent read %s\n", buf);

  }
  return 0;
}
0 голосов
/ 28 октября 2018

Проще говоря, ваш шаблон кода является мусором.Позвольте мне объяснить, почему.

  1. Каждый канал является однонаправленным.

    Если вы используете канал для отправки данных от дочернего элемента к родительскому, закройте конец чтения у дочернего элементаи конец записи в родительском.Это позволяет родителю видеть, когда дочерний элемент (конец записи) закрывает канал или завершает работу, так как read() вернет -1 с errno == EPIPE.

    Если вы используете канал для отправки данных изродительский для дочернего, закройте конец чтения в родительском и конец записи в дочернем.Это позволяет родителю определить, выходит ли ребенок преждевременно, так как write() затем вернется с -1 с errno == EPIPE и в родителе появится сигнал SIGPIPE.

  2. Если вам нужна двунаправленная «труба» между родителем и потомком, используйте пару сокетов потока домена Unix через socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair).

    Такая пара сокетов очень похожа на трубуза исключением того, что пара сокетов является двунаправленной.Вы также можете использовать send(descriptor, buffer, length, MSG_NOSIGNAL) вместо write(descriptor, buffer, length);в первом случае сигнал SIGPIPE не подается, если другой конец сокета уже закрыт.

    Используйте один из дескрипторов в родительском, а другой - в дочернем.И родитель, и ребенок должны закрыть другой дескриптор.В противном случае один конец не сможет определить, когда другой конец закрыл свой дескриптор.

    В некоторых случаях пара сокетов дейтаграммы домена Unix может быть предпочтительнее.Каждый send() генерирует отдельную дейтаграмму, полученную с использованием одного recv().(То есть границы сообщения сохраняются.) Если принимающая сторона знает максимальный размер дейтаграммы, которую может отправлять отправляющая сторона, это чрезвычайно надежный и простой способ реализации двунаправленной связи между родительским и дочерним процессами;Лично я им часто пользуюсь.

  3. read() и write() для труб и розеток могут быть короткие .

    (POSIX заявляет, что вы всегда должны иметь возможность загружать как минимум 512 байт в канал, хотя Linux поддерживает, по крайней мере, до полной страницы, если я правильно помню.)

    Это означает, что вместо одноговызов, вам нужно сделать цикл, пока у вас не будет столько данных, сколько вам нужно.

    С сокетами send() либо отправляет все данные, либо завершается с ошибкой -1errno == EMSGSIZE или некоторымидругой код ошибки).

    Для сокетов дейтаграмм (сокеты дейтаграмм домена Unix, сокеты UDP), если буфер достаточно большой, recv() либо получает всю дейтаграмму, либо завершается с ошибкой -1 (с * 1057)* задавать).Получение дейтаграмм нулевой длины ненадежно, поэтому не пытайтесь это делать.

    Для потоковых сокетов recv() может возвращать только некоторые данные (т. Е. Короткий или частичный прием).

  4. Когда два процесса отправляют и получают или считывают и записывают данные друг другу, взаимоблокировка является серьезной, распространенной проблемой.

    Проще говоря, оба конца могут закончить ожиданием одновременного чтения / записи на другом конце, при этом ничего не происходит.

    Существует три типичных решения, позволяющих избежать тупика в таких ситуациях:

    1. Используйте протокол запроса-ответа, чтобы одна конечная точка всегда инициировала связь, а затем ожидала ответа другой конечной точки.Не более одной конечной точки передает данные в любой момент времени.

    2. Используйте неблокирующий / асинхронный ввод-вывод.То есть перед попыткой write() / send() каждая конечная точка делает read() / recv(), чтобы увидеть, отправил ли другой конец что-либо еще.Это поддерживает полнодуплексную связь (информация может передаваться в обоих направлениях одновременно).

    3. Использовать отдельный поток для непрерывного read() / recv(), а другой - write() /send().Это по существу разделяет каждую розетку на две однонаправленные «дорожки», причем один поток обрабатывает только их направление.Это полезно для протоколов, когда один конец генерирует много данных, а другой отправляет случайные команды.

Объединив все вышесказанное, мы обнаружим, что нет единого шаблона, который следует использовать.Существуют варианты со значительными различиями, которые делают их лучше в одних случаях, но тяжелее / хуже в других.Нужно выбрать один в зависимости от ситуации под рукой.Если OP хочет увидеть лучший пример («шаблон»), они должны описать реальный проблемный случай, включая желаемое поведение.

...