Исходный код Порт
Функции для взаимодействия с внешним миром через порты.
Порты предоставляют механизм для запуска процессов операционной системы, внешних по отношению к Erlang VM, и общения с ними посредством обмена сообщениями.
Пример
iex> port = Port.open({:spawn, "cat"}, [:binary])
iex> send(port, {self(), {:command, "hello"}})
iex> send(port, {self(), {:command, "world"}})
iex> flush()
{#Port<0.1444>, {:data, "hello"}}
{#Port<0.1444>, {:data, "world"}}
iex> send(port, {self(), :close})
:ok
iex> flush()
{#Port<0.1444>, :closed}
:ok
В примере выше мы создали новый порт, который выполняет программу cat. cat — это программа, доступная в операционных системах семейства Unix, которая получает данные с нескольких входов и конкатенирует их в выходе.
После создания порта мы отправили ему два командных сообщения с помощью send/2. Первое сообщение содержит бинарную нагрузку "hello", а второе — "world".
После отправки этих двух сообщений мы вызвали вспомогательную функцию IEx flush(), которая вывела все полученные от порта сообщения, в данном случае "hello" и "world". Обратите внимание, что сообщения являются бинарными, так как мы использовали опцию :binary при открытии порта в Port.open/2. Без этой опции мы бы получили список байтов.
После завершения всех операций мы закрыли порт.
Elixir предоставляет множество удобств для работы с портами и некоторые недостатки. Мы рассмотрим их ниже.
API сообщений и функций
Для работы с портами существуют два API. Можно использовать асинхронный обмен сообщениями, как в примере выше, или вызывать функции этого модуля.
Ниже приведены поддерживаемые портами сообщения и соответствующие функции API:
{pid, {:command, binary}}- отправляет заданные данные в порт. См.command/3.{pid, :close}- закрывает порт. Если порт еще не закрыт, он ответит сообщением{port, :closed}после сброса буферов и фактического закрытия. См.close/1.{pid, {:connect, new_pid}}- устанавливаетnew_pidв качестве нового владельца порта. После открытия порта порт связывается и подключается к вызывающему процессу, и общение с портом происходит только через подключенный процесс. Это сообщение делаетnew_pidновым подключенным процессом. Если порт не завершен, он отправит старому владельцу сообщение{port, :connected}. См.connect/2.
В свою очередь, порт отправит подключенному процессу следующие сообщения:
-
{port, {:data, data}}- данные, отправленные портом -
{port, :closed}- ответ на сообщение{pid, :close} -
{port, :connected}- ответ на сообщение{pid, {:connect, new_pid}} -
{:EXIT, port, reason}- сигналы выхода в случае сбоя порта. Если причина не:normal, это сообщение будет получено только если процесс-владелец обрабатывает сигналы выхода
Механизмы открытия
Порт может быть открыт четырьмя основными способами.
В качестве краткого резюме, предпочтительно использовать опции :spawn и :spawn_executable, упомянутые ниже. Другие две опции, :spawn_driver и :fd, предназначены для расширенного использования внутри VM. Также рассмотрите использование System.cmd/3, если всё, что вам нужно, это запуск программы и получение её возвращаемого значения.
Разделение аргументов в Windows и небезопасные аргументы
В системах Unix аргументы передаются новому процессу операционной системы в виде массива строк, но в Windows это зависит от дочернего процесса, который должен их анализировать, и некоторые программы Windows могут применять свои правила, которые несовместимы со стандартным парсером C runtime
argvЭто особенно проблематично при вызове файлов
.batили.com, так как они неявно запускаются черезcmd.exe, чья обработка аргументов уязвима к вредоносным входным данным и может использоваться для запуска произвольных команд оболочки.Поэтому, если вы работаете в Windows и запускаете пакетные файлы или приложения
.com, вы не должны передавать небезопасные данные в качестве аргументов программе. Вы можете избежать случайного их исполнения, явно указывая расширение программы, которую вы хотите запустить, например,.exe, и дважды проверить, что программа действительно не является пакетным файлом или приложением.com.Это влияет как на
spawn, так и наspawn_executable.
spawn
Кортеж :spawn получает двоичный файл, который будет выполнен как полная команда. Например, мы можем использовать его для прямого вызова "echo hello":
iex> port = Port.open({:spawn, "echo hello"}, [:binary])
iex> flush()
{#Port<0.1444>, {:data, "hello\n"}}
:spawn извлечёт имя программы из аргумента и пройдёт по переменной окружения $PATH операционной системы в поисках соответствующей программы.
Хотя это удобно, это означает, что невозможно вызвать исполняемый файл, имеющий пробелы в имени или в одном из аргументов. По этим причинам чаще всего предпочтительно использовать :spawn_executable.
spawn_executable
Функция `spawn_executable` — более ограниченная и явная версия `spawn`. Она ожидает полные пути к исполняемому файлу, который вы хотите запустить. Если они находятся в вашей переменной $PATH, их можно получить, вызвав System.find_executable/1:
iex> path = System.find_executable("echo")
iex> port = Port.open({:spawn_executable, path}, [:binary, args: ["hello world"]])
iex> flush()
{#Port<0.1380>, {:data, "hello world\n"}}
При использовании :spawn_executable, список аргументов может быть передан через опцию :args, как показано выше. Полный список опций см. в документации для функции Erlang :erlang.open_port/2.
fd
Опция имени :fd позволяет разработчикам получить доступ к дескрипторам файлов in и out, используемых Erlang VM. Вы будете использовать их только в том случае, если вы переписываете ключевую часть системы выполнения, такие как процессы :user и :shell.
Процессы операционной системы-зомби
Порт можно закрыть с помощью функции close/1 или отправки сообщения {pid, :close}. Однако если VM аварийно завершится, долго выполняемая программа, запущенная портом, будет иметь закрытыми каналы stdin и stdout, но не будет автоматически завершена.
Хотя большинство инструментов командной строки Unix завершаются после закрытия каналов связи, не все приложения командной строки это делают. Вы можете легко проверить это, запустив порт, а затем отключив VM и проверив в вашей операционной системе, всё ли ещё работает.
Мы рекомендуем использовать плавную остановку, определяя, закрыты ли stdin/stdout, но мы не всегда имеем контроль над тем, как завершается стороннее программное обеспечение. В таких случаях вы можете обернуть приложение в скрипт, который проверяет stdin. Вот такой скрипт, который протестирован на работе в bash:
#!/usr/bin/env bash # Start the program in the background exec "$@" & pid1=$! # Silence warnings from here on exec >/dev/null 2>&1 # Read from stdin in the background and # kill running program when stdin closes exec 0<&0 $( while read; do :; done kill -KILL $pid1 ) & pid2=$! # Clean up wait $pid1 ret=$? kill -KILL $pid2 exit $ret
Обратите внимание, что программа выше перехватывает stdin, поэтому вы не сможете общаться с базовым ПО через stdin (с другой стороны, ПО, которое читает из stdin, обычно завершается при закрытии stdin).
Теперь вместо:
Port.open(
{:spawn_executable, "/path/to/program"},
args: ["a", "b", "c"]
)
Вы можете вызвать:
Port.open(
{:spawn_executable, "/path/to/wrapper"},
args: ["/path/to/program", "a", "b", "c"]
) Резюме
Типы
Функции
- close(порт)
Закрывает
port.- command(порт, данные, опции \\ [])
Отправляет
dataдрайверу портаport.- connect(порт, pid)
Связывает идентификатор
portсpid.- demonitor(ссылка_монитора, опции \\ [])
Демонизирует монитор, идентифицированный заданной
reference.- info(порт)
Возвращает информацию о
port(илиnilесли порт закрыт).- info(порт, спецификация)
Возвращает информацию о конкретном поле в
port(илиnilесли порт закрыт).- list()
Возвращает список всех портов в текущем узле.
- monitor(порт)
Начинает мониторинг заданного
portиз вызывающего процесса.- open(имя, опции)
Открывает порт, используя кортеж
nameи списокoptions.
Типы
name()Исходный код
@type name() ::
{:spawn, charlist() | binary()}
| {:spawn_driver, charlist() | binary()}
| {:spawn_executable, :file.name_all()}
| {:fd, non_neg_integer(), non_neg_integer()} Функции
close(port)Source
@spec close(port()) :: true
Закрывает port.
Для получения дополнительной информации см. :erlang.port_close/1.
Встраивается компилятором.
command(port, data, options \\ [])Source
@spec command(port(), iodata(), [:force | :nosuspend]) :: boolean()
Отправляет data драйверу порта port.
Для получения дополнительной информации см. :erlang.port_command/3.
Встраивается компилятором.
connect(port, pid)Source
@spec connect(port(), pid()) :: true
Связывает идентификатор port с pid.
Для получения дополнительной информации см. :erlang.port_connect/2.
Встраивается компилятором.
demonitor(monitor_ref, options \\ [])Source
@spec demonitor(reference(), options :: [:flush | :info]) :: boolean()
Отключает мониторинг, идентифицированный данным reference.
Если monitor_ref является ссылкой, полученной вызывающим процессом с помощью вызова monitor/1, то мониторинг отключается. Если мониторинг уже отключен, ничего не происходит.
См. :erlang.demonitor/2 для получения дополнительной информации.
Встраивается компилятором.
info(port)Source
@spec info(port()) :: keyword() | nil
Возвращает информацию о port (или nil если порт закрыт).
Для получения дополнительной информации см. :erlang.port_info/1.
info(port, spec)Source
@spec info(port(), atom()) :: {atom(), term()} | nil Возвращает информацию о конкретном поле в port (или nil если порт закрыт).
Для получения дополнительной информации см. :erlang.port_info/2.
list()Source
@spec list() :: [port()]
Возвращает список всех портов в текущем узле.
Встраивается компилятором.
monitor(port)Source
@spec monitor(port() | {name, node()} | name) :: reference() when name: atom() Начинает мониторинг заданного port из вызывающего процесса.
После завершения отслеживаемого процесса порта сообщение доставляется отслеживающему процессу в форме:
{:DOWN, ref, :port, object, reason}
где:
-
ref— ссылка на монитор, возвращаемая этой функцией; -
object— либоportотслеживаемый объект (при мониторинге по идентификатору порта), либо{name, node}(при мониторинге по имени порта); -
reason— причина завершения.
См. :erlang.monitor/2 для получения дополнительной информации.
Встраивается компилятором.
open(name, options)Source
@spec open(name(), list()) :: port()
Открывает порт, заданный кортежем name и списком options.
Документация модуля выше содержит документацию и примеры поддерживаемых значений name, краткое описание приведено ниже:
-
{:spawn, command}— запускает внешнюю программу.commandдолжен содержать имя программы и, необязательно, список аргументов, разделенных пробелом. Если передаются программы или аргументы с пробелами в имени, используйте следующий вариант. -
{:spawn_executable, filename}— запускает исполняемый файл, заданный абсолютным именемfilename. Аргументы могут быть переданы через опцию:args. -
{:spawn_driver, command}— запускает так называемые драйверы портов. -
{:fd, fd_in, fd_out}— получает доступ к дескрипторам файлов,fd_inиfd_outоткрытым виртуальной машиной.
Для получения дополнительной информации и списка опций, см. :erlang.open_port/2.
Встраивается компилятором.
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.17.2/Port.html