Порт
Функции для взаимодействия с внешним миром через порты.
Порты предоставляют механизм запуска процессов операционной системы, внешних по отношению к виртуальной машине Erlang, и взаимодействия с ними посредством обмена сообщениями.
Пример
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. Вы будете использовать их только в том случае, если вы переопределяете основную часть системы выполнения, такие как процессы :user и :shell.
Процессы-зомби
Порт можно закрыть с помощью функции close/1 или отправкой сообщения {pid, :close}. Однако, если виртуальная машина аварийно завершает работу, долго выполняемая программа, запущенная портом, будет иметь закрытыми каналы 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, settings)
-
Открывает порт, задавая кортеж
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 \\ [])
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)
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, settings)
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.6.6/Port.html