Как сделать так, чтобы запись в именованный канал блокировалась, если читатель закрыт, и наоборот?

Вопрос или проблема

Сейчас, если я пишу в именованный канал и затем закрываю читателя, писатель получает SIGPIPE и затем завершается. Например,

$ mkfifo pipe
$ cat pipe & # чтение из канала в фоне
$ cat > pipe # запись в канал
line1
line2
...

Если я затем останавливаю процесс читателя, процесс писателя завершается из-за SIGPIPE, который читатель послал при остановке.

Я не хочу, чтобы это происходило. Если писатель пытается записать в канал до появления читателя, писатель блокируется и ждет появления читателя. Я хочу такое поведение. Если читатель закрывает канал, писатель должен блокироваться и ждать нового читателя.

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

Есть ли у cat флаг для этого? Если нет, это должно быть довольно простая программа на C.

man 2 pipe говорит:

Если все файловые дескрипторы, относящиеся к концу записи канала, были закрыты, то попытка чтения (read(2)) из канала увидит конец файла (read(2) вернет 0). Если все файловые дескрипторы, относящиеся к концу чтения канала, были закрыты, то запись (write(2)) вызовет генерацию сигнала SIGPIPE для вызывающего процесса. Если вызывающий процесс игнорирует этот сигнал, то write(2) завершится с ошибкой EPIPE.

Почему бы вам не игнорировать SIGPIPE и просто не переоткрыть соединение, когда получаете EOF (со стороны читателя) или EPIPE (со стороны писателя)?

Вам в любом случае понадобится программа на C. Со стороны записи:

#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

int main(int argc, char **argv) {
  signal(SIGPIPE, SIG_IGN);  // Игнорировать SIGPIPE, чтобы предотвратить завершение
  int i = 0;

  while (1) {
    printf("Открытие %s\n", argv[1]);
    FILE *fd = fopen(argv[1], "w");
    if (fd == NULL) {
      perror (argv[1]);
      exit(errno);
    }

    while(1) {
      if (-1 == fprintf(fd, "%d\n", i++)) {
        if (errno == EPIPE) {
          fclose(fd);
          break;
        }
      }
      fflush(fd);
      sleep(1);
    }
  }
  return 0;
}

Для программы чтения:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv) {
  int i = 0;
  char s[100];

  while (1) {
    printf("Открытие %s\n", argv[1]);
    FILE *fd = fopen(argv[1], "r");
    if (fd == NULL) {
      perror (argv[1]);
      exit(errno);
    }

    while(1) {
      if (NULL == fgets(s, sizeof s, fd)) {
        fclose(fd);
        break;
      }
      fputs(s, stdout);
    }
  }
  return 0;
}

Результат, когда вы завершаете процесс записи, или завершаете процесс чтения, пока процесс записи все еще идет:

16:15:30:~/src$ ./readpipe /tmp/myfifo
16:15:50
Открытие /tmp/myfifo
0
1
2
3
4
Открытие /tmp/myfifo
0
1
2
3
4
5
Открытие /tmp/myfifo
0
1
2
3
4
^C
16:16:09:~/src$ ./readpipe /tmp/myfifo
16:16:10
Открытие /tmp/myfifo
6
7
8
9
^C
16:16:13:~/src$ ./readpipe /tmp/myfifo
16:16:14
Открытие /tmp/myfifo
11
12
13
14
^C
16:16:19:~/src$
16:15:46:~/src$ ./writepipe /tmp/myfifo
16:15:53
Открытие /tmp/myfifo
^C
16:15:57:~/src$ ./writepipe /tmp/myfifo
16:15:58
Открытие /tmp/myfifo
^C
16:16:03:~/src$ ./writepipe /tmp/myfifo
16:16:04
Открытие /tmp/myfifo
^C

Ответ или решение

Чтобы управлять блокировкой записей в именованный канал (FIFO), когда один из процессов (читатель или писатель) закрывается, и избежать завершения работы процессов из-за сигнала SIGPIPE, необходимо разработать специфическое решение на уровне программирования. Это требует понимания работы сигналов в Unix и управления вводом-выводом. Давайте подробно рассмотрим, как можно добиться такого поведения.

Теория

Именованный канал (FIFO) представляет собой межпроцессное средство передачи данных, позволяющее передавать данные между связанными процессами. Однако работа с ним имеет определенные ограничения:

  1. SIGPIPE для писателя – Когда читатель закрывает конец канала, писатель, пытающийся записать данные, получает сигнал SIGPIPE и может завершиться, если этот сигнал не обработан.
  2. EOF для читателя – Когда писатель закрывает конец канала, читатель, пытающийся прочитать данные, получает конец файла (EOF), что может привести к завершению чтения.

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

Пример

Рассмотрим пример кода, который демонстрирует необходимое поведение:

Программа для записи

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

// Главная функция для программы записи
int main(int argc, char **argv) {
    // Игнорирование сигнала SIGPIPE для предотвращения завершения работы
    signal(SIGPIPE, SIG_IGN);

    int i = 0;
    while (1) {
        printf("Открытие %s для записи\n", argv[1]);
        FILE *fd = fopen(argv[1], "w");

        if (fd == NULL) {
            perror(argv[1]);
            exit(errno);
        }

        while(1) {
            if (fprintf(fd, "%d\n", i++) == -1) {
                if (errno == EPIPE) {
                    fclose(fd);
                    break;
                }
            }
            fflush(fd);
            sleep(1);
        }
    }
    return 0;
}

Программа для чтения

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv) {
    char s[100];

    while (1) {
        printf("Открытие %s для чтения\n", argv[1]);
        FILE *fd = fopen(argv[1], "r");

        if (fd == NULL) {
            perror(argv[1]);
            exit(errno);
        }

        while(1) {
            if (fgets(s, sizeof s, fd) == NULL) {
                fclose(fd);
                break;
            }
            fputs(s, stdout);
        }
    }
    return 0;
}

Применение

  1. Игнорирование SIGPIPE: Сигнал SIGPIPE генерируется, когда нет ни одного открытого приемника на другом конце канала. Устанавливая обработчик signal(SIGPIPE, SIG_IGN), мы предотвращаем завершение процесса записи.

  2. Переподключение: Когда одна из сторон закрывается, другая сторона закрывает свой файловый дескриптор и возвращается в режим ожидания, пытаясь переподключиться. Это позволяет процессам вновь начинать обмен данными, как только другая сторона стала доступной.

  3. Блокировка: В ситуации, когда одна сторона канала временно отсутствует, программа просто закрывает текущее соединение и ожидает повторного открытия канала, имитируя желаемое поведение "блокировки", пока не появится другой процесс.

Таким образом, мы определили метод, позволяющий программно реализовать блокировку операций записи и чтения в случае закрытия одной из сторон в именованных каналах. Этот подход требует написания собственных программ вероятнее всего на С или другом системном языке, что позволяет полностью управлять поведением при межпроцессном взаимодействии.

Оцените материал
Добавить комментарий

Капча загружается...