Статьи

Proc_Open: общение с внешним миром

Существует много способов взаимодействия с другими приложениями из PHP и обмена данными; есть веб-сервисы, системы очередей сообщений, сокеты, временные файлы, exec() и т. д. Что ж, сегодня я хотел бы показать вам один конкретный подход, proc_open() . Функция порождает новую команду, но с указателями открытого файла, которые можно использовать для отправки и получения данных для достижения межпроцессного взаимодействия (IPC).

Что за труба?

Чтобы понять, как proc_open() и процесс отправляет и получает данные, вы знаете, что такое канал.

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

Если вы когда-либо работали в оболочке Unix, то есть большая вероятность, что вы использовали каналы, возможно, даже не осознавая этого. Например:

  $ mysql -u dbuser -p test <mydata.sql 

Здесь вызывается утилита mysql и система устанавливает канал, через который содержимое файла mydata.sql подается в mysql как если бы вы mydata.sql прямо с его клавиатуры.

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

Независимо от типа, важным свойством любого канала является то, что это структура FIFO (первым вошел , первым вышел). Это означает, что данные, записанные в канал первым одним процессом, являются первыми данными, считанными из канала другим процессом.

Представляем proc_open ()

PHP-функция proc_open() выполняет команду, как и exec() , но с дополнительной возможностью направлять потоки ввода и вывода через каналы. Он принимает некоторые необязательные аргументы, но обязательные аргументы:

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

Необязательные аргументы используются для настройки среды выполнения, в которой создается команда. Я не буду обсуждать это здесь, но вы можете найти больше информации в руководстве по PHP.

Помимо команды для выполнения, я бы сказал, что массив дескрипторов, который определяет каналы, является наиболее важным аргументом, на который следует обратить внимание. Документация объясняет это как «индексированный массив, где ключ представляет номер дескриптора, а значение представляет, как PHP передает этот дескриптор дочернему процессу», но что именно это означает?

Три основных потока данных для хорошо работающего Unix-процесса: STDIN (стандартный ввод), STDOUT (стандартный вывод) и STDERR (стандартная ошибка). То есть существует поток для входящих данных, один для исходящих данных и второй исходящий поток для информационных сообщений. STDIN традиционно представлен целым числом 0, STDOUT — 1, а STDERR — 2. Таким образом, определение с ключом 0 будет использоваться для настройки входного потока, 1 для выходного потока и 2 для потока ошибок.

Сами эти определения могут принимать одну из двух форм: либо открытый файловый ресурс, либо массив, который описывает природу канала. Для анонимного канала первый элемент описательного массива — это строка «канал», а второй — «r», «w» или «a», в зависимости от того, должен ли канал считываться, записываться или добавляться. Для именованных каналов описательный массив содержит строку «file», имя файла, а затем «r», «w» или «a».

После proc_open() заполняет ссылку на массив третьего параметра, чтобы вернуть ресурсы процессу. Элементы в ссылке могут рассматриваться как обычные файловые дескрипторы, и они работают с файловыми и потоковыми функциями, такими как fwrite() , fread() , stream_get_contents() и т. Д.

Когда вы закончите взаимодействовать с внешней командой, важно убирать за собой. Вы можете закрыть каналы (с помощью fclose() ) и ресурса процесса (с помощью proc_close() ).

В зависимости от того, как ведет себя ваша целевая команда / процесс, вам может потребоваться закрыть соединение STDIN до того, как оно начнет свою работу (чтобы он не ожидал больше ввода). И вы должны закрыть соединения STDOUT и STDERR перед закрытием процесса, иначе он может зависнуть, пока он ждет, пока все будет чисто, прежде чем завершить работу.

Практический пример: преобразование вики-разметки

До сих пор я только говорил о том, как все работает, но я еще не показал вам пример использования proc_open() для порождения и взаимодействия с внешним процессом. Итак, давайте посмотрим, насколько легко им пользоваться.

Предположим, нам нужно преобразовать фрагмент текста с вики-разметкой в ​​HTML для отображения в браузере пользователя. Мы используем Nyctergatis Markup Engine (NME) для выполнения преобразования, но, поскольку это скомпилированный двоичный файл C, нам нужен способ nme когда это необходимо, и способ передачи ввода и получения вывода.

 <?php // descriptor array $desc = array( 0 => array('pipe', 'r'), // 0 is STDIN for process 1 => array('pipe', 'w'), // 1 is STDOUT for process 2 => array('file', '/tmp/error-output.txt', 'a') // 2 is STDERR for process ); // command to invoke markup engine $cmd = "nme --strictcreole --autourllink --body --xref"; // spawn the process $p = proc_open($cmd, $desc, $pipes); // send the wiki content as input to the markup engine // and then close the input pipe so the engine knows // not to expect more input and can start processing fwrite($pipes[0], $content); fclose($pipes[0]); // read the output from the engine $html = stream_get_contents($pipes[1]); // all done! Clean up fclose($pipes[1]); fclose($pipes[2]); proc_close($p); 

Сначала создается массив дескрипторов, где 0 (STDIN) — это анонимный канал, который будет читаться механизмом разметки, 1 (STDOUT) — как анонимный канал, доступный для записи механизмом, и 2 (STDERR), перенаправляющий любые сообщения об ошибках в файл журнала ошибок.

«R» и «w» в определениях каналов на первый взгляд могут показаться нелогичными, но имейте в виду, что они являются каналами, которые будет использовать движок, и поэтому настроены с его точки зрения. Мы пишем в канал чтения, потому что движок будет читать данные из него. Мы читаем из канала записи, потому что движок записал в него данные.

Вывод

Есть много способов взаимодействия с внешними процессами; некоторые из них могут быть лучше, чем использование proc_open() учитывая, как и с чем вам нужно работать, или proc_open() может быть просто тем, что доктор прописал для вашей ситуации. Конечно, вы реализуете то, что имеет смысл, но теперь вы будете знать, как использовать эту мощную функцию, если вам нужно!

Я поместил пример кода на GitHub, который имитирует вики с использованием NME, как в примере выше. Не стесняйтесь клонировать его, если вам интересно играть и исследовать дальше.

Изображение через Fotolia