Порт
Функции для взаимодействия с внешним миром через порты.
Порты предоставляют механизм запуска процессов операционной системы, внешних по отношению к 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/sh "$@" & 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"]]) Краткое описание
Типы
- name()
Функции
- 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()
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 \\ []) (с версии 1.6.0)
demonitor(reference(), options :: [:flush | :info]) :: boolean()
Демонизирует монитор, идентифицируемый заданным reference.
Если monitor_ref — это ссылка, полученная вызывающим процессом с помощью вызова monitor/1, этот мониторинг выключается. Если мониторинг уже отключен, ничего не происходит.
См. :erlang.demonitor/2 для получения дополнительной информации.
Встроено компилятором.
info(port)
Возвращает информацию о 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) (с версии 1.6.0)
monitor(port() | {name :: atom(), node :: atom()} | name() :: atom()) ::
reference() Начинает мониторинг заданного 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.7.4/Port.html