Spec-Zone.ru › Elixir 1.16

Исходный код Порт

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

Порты предоставляют механизм для запуска процессов операционной системы, внешних по отношению к виртуальной машине 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, которая получает данные с нескольких входов и конкатенирует их в выходе.

После создания порта мы отправили ему две команды в виде сообщений с помощью 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, если всё, что вам нужно, — это выполнить программу и получить её возвращаемое значение.

Разделение аргументов на 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. Вы будете использовать их только если переопределяете основные части системы выполнения, такие как процессы :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"]
)

Сводка

Типы

name()

Функции

close(порт)

Закрывает port.

command(порт, данные, опции \\ [])

Отправляет data драйверу порта port.

connect(порт, pid)

Связывает идентификатор port с pid.

demonitor(ссылка_на_монитор, опции \\ [])

Демонизирует монитор, идентифицированный заданной reference.

info(порт)

Возвращает информацию о port (или nil если порт закрыт).

info(порт, спецификация)

Возвращает информацию об определённом поле в port (или nil если порт закрыт).

list()

Возвращает список всех портов в текущем узле.

monitor(порт)

Начинает мониторинг указанного port из вызывающего процесса.

open(имя, опции)

Открывает порт, заданный кортежем name и списком options.

Типы

name()Source

@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.

Встроено компилятором.

Скачать версию ePub

Создано с помощью ExDoc (v0.32.2) для языка программирования Elixir

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.16.3/Port.html

Spec-Zone.ru

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