Разделить именованный канал на два конца: один только для чтения, один только для записи.

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

Я пытаюсь настроить межпроцессное взаимодействие, и как первая идея, я хочу использовать именованный канал для передачи сообщений от одного процесса к другому. Проблема, с которой я сталкиваюсь, заключается в обеспечении того, чтобы первый процесс только записывал в канал, а второй процесс только читал из канала. Обычно я мог бы просто написать программу, чтобы первый процесс только записывал в канал, и наоборот для второго процесса, но я несколько параноичен и хочу, чтобы программа выдавала ошибку, если она когда-либо даже пытается открыть канал в неправильном режиме.

По сути, я пытаюсь “разделить” канал пополам, так чтобы конец записи был с моей первой программой:

program1/
├── write-end-of-pipe
└── program1.c

и чтобы конец чтения был со второй программой:

program2/
├── read-end-of-pipe
└── program2.c

Идеально, если бы могли существовать реальные файлы на файловой системе для концов записи/чтения канала.


На данный момент, мое лучшее решение следующее:

  1. Создать именованный канал в program1 и изменить разрешения файла так, чтобы он был доступен только для записи этим пользователем:

    cd ~/program1
    mkfifo write-end-of-pipe
    chmod 200 write-end-of-pipe
    
  2. Создать именованный канал в program2 и изменить разрешения файла так, чтобы он был доступен только для чтения этим пользователем:

    cd ~/program2
    mkfifo read-end-of-pipe
    chmod 400 read-end-of-pipe
    
  3. Создать нового пользователя для маршрутизации и запустить демон, который будет переправлять данные из первого именованного канала во второй:
    (Предполагается, что пользователь уже создан:)

    su - forwarding-daemon-user
    chmod 400 ~/program1/write-end-of-pipe
    chmod 200 ~/program2/read-end-of-pipe
    cat write-end-of-pipe > read-end-of-pipe &
    

Проблема, с которой я сталкиваюсь, двоякая:

  1. Требуется два канала вместо одного.
  2. Требуется демон. (Моя команда cat недостаточно надёжна сама по себе, но она передаёт суть.)

Есть ли более лучший способ создать два файла для канала, один из которых можно только записывать, а другой — только читать?


Редактирование 1: Для некоторого контекста о том, для чего я хотел бы использовать это межпроцессное взаимодействие: Я думаю, было бы круто, если бы я мог реализовать “шэринг экрана,” где один процесс, написанный на одном языке, создаёт экран (по сути видеопоток), а другой процесс на совершенно другом языке отображает полученный экран. (Пример использования: один язык делает лёгким создание игры, но вы хотите написать GUI на другом языке.) Причина моей паранойи по поводу записи/чтения заключается в том, что я хочу иметь возможность заменять различные программы, создающие или просматривающие экраны, и не хочу беспокоиться о подключении двух создателей экранов вместе (если я предоставляю конец чтения и конец записи, тогда одна программа получает неверный конец, если я сделал такую ошибку, и ошибка обнаруживается немедленно, а не позже, когда канал переполняется) или другие неработающие программы, вызывающие скрытые проблемы.

Редактирование 2: Я считаю, что должен объяснить, почему наличие двух отдельных пользователей для чтения/записи не является идеальным решением для меня. Главная причина заключается в том, что это значительный шаг на пути к контейнеризации. Как только вы скажете: “Я просто буду управлять разрешениями через отдельных пользователей/пространства имён,” вы должны понять, как вы собираетесь настраивать этих пользователей/пространства имён, и это, по сути, означает помещать программу в контейнер. Я хочу избежать этого, потому что это не так гибко; внезапно каждая программа теперь нуждается в стандартизированном способе запуска, а именованный канал, соединяющий две программы, должен создаваться специальным пользователем, который может заглядывать в эти контейнеры и т.д.

Таким образом, я бы предпочёл избежать использования нескольких пользователей и подобные техники, требующие обёртывания программы, которую нужно вызвать. Решение, которое будет работать хорошо – это если бы был способ “монтировать” именованный канал так, чтобы у него были разные разрешения на файл из двух разных мест для записи/чтения, но я не думаю, что это возможно в Linux.

Если бы я абсолютно хотел гарантировать, что ситуация с двойной записью или двойным чтением не может произойти, я бы просто перешёл на использование сокетов (либо TCP, либо UNIX).

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

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


Итог: fifos подходят для быстрого создания чего-то, но для надёжного решения я бы их игнорировал.

С другой стороны, вы должны спросить “является ли это реальной проблемой”?
Затраты на появление проблемы/затраты на её исправление настолько минимальны, что, вероятно, не стоит моего времени, чтобы этого избежать.

Однако использование TCP-сокетов даёт мне гибкость для запуска читателя и писателя на разных машинах, что я часто считаю очень полезным (для отладки и т.д.), даже если окончательное производство будет всегда на одной машине.

├── write-end-of-pipe
├── read-end-of-pipe

Нет, этого не произойдет с именованными каналами. Их суть в том, что они одно “видимое имя” для обоих концов.

Итак, если это то, что вы хотите, то это неверная технология.

Для действительно однонаправленных сообщений с файловой семантикой, вы могли бы позволить двум процессам соединяться через двунаправленные UNIX-сокеты¹. Вы могли бы использовать pipe для открытия каналов, закрывать конец, который они не хотят использовать, и передавать файловые дескрипторы через сокет.

Другой вариант – создать именованный канал, позволить первой программе открыть его в нужном ей режиме и изменить разрешения для того же пользователя так, чтобы он был “только для чтения” или “только для записи”. Это не влияет на дескрипторы файлов, полученные через открытие до изменения разрешений, так что программа продолжает работать, но делает невозможным для “следующей” программы получить доступ к файлу в неправильном режиме. Однако, в этом есть очевидная временная гонка.

Для простых однонаправленных сообщений: UDP-сокеты сработали бы. Вы можете просто позволить принимающему концу сообщений слушать порт, и другой может отправлять сообщения.


¹ TCP здесь не сработает, потому что вам нужно обмениваться файловыми дескрипторами

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

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

Теория

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

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

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

Пример

Предположим, у вас есть две программы: program1, которая должна только записывать, и program2, которая должна только читать. Вы создаете именованный канал mkfifo mypipe и меняете права доступа, чтобы program1 и program2 могли использовать разные права.

mkfifo ~/program1/write-only-pipe
chmod 0220 ~/program1/write-only-pipe # Только запись для владельца
mkfifo ~/program2/read-only-pipe
chmod 0440 ~/program2/read-only-pipe # Только чтение для владельца

Применение

Реализация через Именованные Каналы

  1. Настройка прав: Вы можете использовать chmod, чтобы изменить права доступа так, чтобы первая программа имела только права на запись, а вторая только на чтение. Однако, это может быть субъективно к изменению конфигурации среднего уровня в течение времени выполнения, что связано с состоянием гонки.

  2. Запуск демона: Ваша текущая реализация с использованием процесса-демона для копирования содержимого из одной трубы в другую демонстрирует вашу точку зрения. Однако, это менее эффективно из-за использования двух труб и необходимости поддержания фонового процесса-демона.

Использование Сокетов

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

  1. Настройте серверный сокет в программе писателя: Писатель будет выступать в роли сервера, принимая соединения только от читателя.

  2. Обработка только одного соединения: Сокеты позволяют установить только одно активное соединение за раз, в отличие от FIFO, где другой процесс мог бы попытаться открыть канал неправильно.

  3. Удобство использования TCP/IP в качестве преимущества отладки: Если коммуникации развернуты через TCP/IP, это позволяет использовать те же функции для отладки и развития в распределенных системах, когда читатель и писатель находятся на разных машинах.

Заключение

Для полной гарантии того, что только писатель записывает и только читатель читает, FIFO может быть неидеальным выбором из-за своей двунаправленной природы и отсутствия встроенной защиты от неправомерного доступа. Сокеты, такие как UNIX Domain Sockets или даже TCP/UDP для различных случаев использования, обеспечивают более гибкий и масштабируемый подход.

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

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

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