当前位置: 首页>>代码示例 >>用法及示例精选 >>正文


Elixir Port用法及代码示例


Elixir语言中 Port 相关用法介绍如下。

通过端口与外部世界交互的函数。

端口提供了一种机制来启动 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-like 操作系统上可用的程序,它接收来自多个输入的数据并将它们连接到输出中。

创建端口后,我们使用 Kernel.send/2 以消息的形式向它发送两个命令。第一个命令的二进制有效负载为"hello",第二个命令为"world"。

发送这两条消息后,我们调用了 IEx 助手 flush() ,它打印了从端口接收到的所有消息,在这种情况下,我们得到了 "hello" 和 "world"。请注意,消息是二进制的,因为我们在 Port.open/2 中打开端口时传递了 :binary 选项。如果没有这样的选项,它将产生一个字节列表。

一切完成后,我们关闭了端口。

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 元组接收将作为完整调用执行的二进制文件。例如,我们可以使用它直接调用"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 可执行文件是 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 名称选项允许开发人员访问 Erlang VM 使用的 inout 文件说明符。只有在重新实现运行时系统的核心部分(例如 :user:shell 进程)时,才会使用这些。

僵尸操作系统进程

可以通过 close/1 函数或发送{pid, :close} 消息来关闭端口。但是,如果 VM 崩溃,由端口启动的 long-running 程序将关闭其标准输入和标准输出通道,但不会自动终止。

虽然大多数 Unix 命令行工具会在其通信通道关闭后退出,但并非所有命令行应用程序都会这样做。您可以通过启动端口然后关闭 VM 并检查您的操作系统以查看端口进程是否仍在运行来轻松检查这一点。

虽然我们通过检测标准输入/标准输出是否已关闭来鼓励正常终止,但我们并不总是能够控制第三方软件的终止方式。在这些情况下,您可以将应用程序包装在检查标准输入的脚本中。这是经过验证可以在 bash shell 上运行的脚本:

#!/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

请注意,上面的程序劫持了标准输入,因此您将无法通过标准输入与底层软件进行通信(从积极的方面来说,从标准输入读取的软件通常会在标准输入关闭时终止)。

现在代替:

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"]
)

相关用法


注:本文由纯净天空筛选整理自elixir-lang.org大神的英文原创作品 Port。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。