查看源代码 Port (Elixir v1.16.2)
用于通过端口与外部世界交互的函数。
端口提供了一种机制来启动 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 操作系统上可用的一个程序,它从多个输入接收数据并将它们连接到输出。
端口创建后,我们使用 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}
- 端口崩溃时的退出信号。如果 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
将从参数中检索程序名称,并遍历您的操作系统 $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 使用的 in
和 out
文件描述符。只有在重新实现运行时系统核心部分(如 :user
和 :shell
进程)时才会使用这些描述符。
僵尸操作系统进程
端口可以通过 close/1
函数或发送 {pid, :close}
消息来关闭。但是,如果 VM 崩溃,端口启动的长时间运行的程序将关闭其 stdin 和 stdout 通道,但**不会自动终止**。
虽然大多数 Unix 命令行工具在关闭其通信通道后会退出,但并非所有命令行应用程序都会这样做。您可以通过启动端口,然后关闭 VM 并检查您的操作系统来轻松验证这一点,以查看端口进程是否仍在运行。
虽然我们鼓励通过检测 stdin/stdout 是否已关闭来优雅地终止,但我们并不总是能够控制第三方软件如何终止。在这些情况下,您可以将应用程序包装在一个检查 stdin 的脚本中。以下是一个脚本,已验证在 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
请注意,上面的程序劫持了 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"]
)
摘要
函数
关闭 port
。
将 data
发送到端口驱动程序 port
。
将 port
标识符与 pid
关联。
取消监视由给定 reference
标识的监视器。
返回有关 port
的信息(如果端口已关闭,则返回 nil
)。
返回有关 port
中特定字段的信息(如果端口已关闭,则返回 nil
)。
返回当前节点中所有端口的列表。
从调用进程开始监视给定的 port
。
使用元组 name
和 options
列表打开端口。
类型
@type name() :: {:spawn, charlist() | binary()} | {:spawn_driver, charlist() | binary()} | {:spawn_executable, :file.name_all()} | {:fd, non_neg_integer(), non_neg_integer()}
函数
@spec close(port()) :: true
关闭 port
。
有关更多信息,请参阅 :erlang.port_close/1
.
由编译器内联。
将 data
发送到端口驱动程序 port
。
有关更多信息,请参阅 :erlang.port_command/3
.
由编译器内联。
将 port
标识符与 pid
关联。
有关更多信息,请参阅 :erlang.port_connect/2
.
由编译器内联。
取消监视由给定 reference
标识的监视器。
如果 monitor_ref
是调用进程通过调用 monitor/1
获得的引用,则该监视将被关闭。如果监视器已关闭,则不会发生任何事情。
有关更多信息,请参阅 :erlang.demonitor/2
.
由编译器内联。
返回有关 port
的信息(如果端口已关闭,则返回 nil
)。
有关更多信息,请参阅 :erlang.port_info/1
.
返回有关 port
中特定字段的信息(如果端口已关闭,则返回 nil
)。
有关更多信息,请参阅 :erlang.port_info/2
.
@spec list() :: [port()]
返回当前节点中所有端口的列表。
由编译器内联。
从调用进程开始监视给定的 port
。
监视的端口进程一旦死亡,一条消息将以以下形式传递给监视进程
{:DOWN, ref, :port, object, reason}
其中
ref
是此函数返回的监视器引用;object
是正在监视的port
(按端口 ID 监视时)或{name, node}
(按端口名称监视时);reason
是退出原因。
有关更多信息,请参阅 :erlang.monitor/2
.
由编译器内联。
使用元组 name
和 options
列表打开端口。
上面的模块文档包含对支持的 name
值的文档和示例,总结如下
{:spawn, command}
- 运行外部程序。command
必须包含程序名称,并可以选择包含以空格分隔的参数列表。如果传递的程序或参数名称中包含空格,请使用下一个选项。{:spawn_executable, filename}
- 运行由绝对文件名filename
指定的可执行文件。可以通过:args
选项传递参数。{:spawn_driver, command}
- 生成所谓的端口驱动程序。{:fd, fd_in, fd_out}
- 访问 VM 打开的文件描述符fd_in
和fd_out
。
有关更多信息和选项列表,请参阅 :erlang.open_port/2
.
由编译器内联。