Spec-Zone.ru › Elixir 1.8

Порт

Функции для взаимодействия с внешним миром через порты.

Порты предоставляют механизм для запуска процессов операционной системы, внешних по отношению к 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, предназначены для расширенного использования внутри VM. Также рассмотрите использование System.cmd/3, если все, что вам нужно, это выполнить программу и получить ее возвращаемое значение.

spawn

Кортеж :spawn получает двоичные данные, которые будут выполнены как полное обращение. Например, мы можем использовать его для прямого вызова "echo hello":

iex> port = Port.open({:spawn, "echo hello"}, [:binary])
iex> flush()
{#Port<0.1444>, {:data, "hello\n"}}

:spawn извлечет имя программы из аргумента и переберёт переменную окружения OS $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"]
)

Резюме

Типы

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, 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.8.2/Port.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API