Порт
Функции для взаимодействия с внешним миром через порты.
Порты предоставляют механизм для запуска процессов операционной системы, внешних по отношению к 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.1464>, :closed}
:ok В примере выше мы создали новый порт, который выполняет программу cat. cat — это программа, доступная в системах UNIX, которая получает данные с нескольких входов и конкатенирует их в выходе.
После создания порта мы отправили ему две команды в виде сообщений, используя Kernel.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, предназначены для расширенного использования внутри виртуальной машины. Также рассмотрите использование System.cmd/3, если вам нужно только выполнить программу и получить её возвращаемое значение.
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 завершатся после закрытия каналов связи, не все приложения командной строки сделают это. Хотя мы рекомендуем плавное завершение, обнаруживая, был ли закрыт stdin/stdout, мы не всегда контролируем то, как завершается стороннее программное обеспечение. В таких случаях вы можете обернуть приложение в скрипт, который проверяет stdin. Вот такой скрипт на Bash:
#!/bin/bash "$@" & pid=$! while read line ; do : done kill -KILL $pid
Теперь вместо:
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)
Закрывает
port.- command(port, data, options \\ [])
Отправляет
dataв драйвер портаport.- connect(port, pid)
Связывает идентификатор
portсpid.- demonitor(monitor_ref, options \\ [])
Отслеживает монитор, идентифицированный заданным
reference.- info(port)
Возвращает информацию о
portилиnil, если порт закрыт.- info(port, spec)
Возвращает информацию о
portилиnil, если порт закрыт.- list()
Возвращает список всех портов в текущем узле.
- monitor(port)
Начинает отслеживание заданного
portс вызывающего процесса.- open(name, options)
Открывает порт, используя кортеж
nameи списокoptions.
Типы
name()
Specs
name() ::
{:spawn, charlist() | binary()}
| {:spawn_driver, charlist() | binary()}
| {:spawn_executable, charlist() | atom()}
| {:fd, non_neg_integer(), non_neg_integer()} Функции
close(port)
Спецификации
close(port()) :: true
Закрывает port.
Для получения дополнительной информации, см. :erlang.port_close/1.
Встроено компилятором.
command(port, data, options \\ [])
Спецификации
command(port(), iodata(), [:force | :nosuspend]) :: boolean()
Отправляет data в драйвер порта port.
Для получения дополнительной информации, см. :erlang.port_command/2.
Встроено компилятором.
connect(port, pid)
Спецификации
connect(port(), pid()) :: true
Связывает идентификатор port с pid.
Для получения дополнительной информации, см. :erlang.port_connect/2.
Встроено компилятором.
demonitor(monitor_ref, options \\ [])
Спецификации
demonitor(reference(), options :: [:flush | :info]) :: boolean()
Отключает мониторинг, идентифицированный заданным reference.
Если monitor_ref — ссылка, полученная вызывающим процессом с помощью вызова monitor/1, то мониторинг отключается. Если мониторинг уже отключен, ничего не происходит.
См. :erlang.demonitor/2 для получения дополнительной информации.
Встроено компилятором.
info(port)
Спецификации
info(port()) :: keyword() | nil
Возвращает информацию о port или nil если порт закрыт.
Для получения дополнительной информации, см. :erlang.port_info/1.
info(port, spec)
Спецификации
info(port(), atom()) :: {atom(), term()} | nil Возвращает информацию о port или nil если порт закрыт.
Для получения дополнительной информации, см. :erlang.port_info/2.
list()
Спецификации
list() :: [port()]
Возвращает список всех портов в текущем узле.
Встроено компилятором.
monitor(port)
Спецификации
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)
Спецификации
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 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.9.4/Port.html