檢視原始碼 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

在上面的範例中,我們建立了一個新的埠,執行程式 catcat 是 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} - 埠發生異常時的結束訊號。如果原因不是 :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 使用的 inout 檔案描述子。只有在重新實作執行時期系統的核心部分(例如 :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

由編譯器內嵌。

連結至這個函數

command(port, data, options \\ [])

檢視原始碼
@spec command(port(), iodata(), [:force | :nosuspend]) :: boolean()

data 傳送至埠驅動程式 port

如需更多資訊,請參閱 :erlang.port_command/3

由編譯器內嵌。

@spec connect(port(), pid()) :: true

port 識別碼與 pid 關聯。

如需更多資訊,請參閱 :erlang.port_connect/2

由編譯器內嵌。

連結至這個函數

demonitor(monitor_ref, options \\ [])

檢視原始碼 (自 1.6.0 起)
@spec demonitor(reference(), options :: [:flush | :info]) :: boolean()

取消監控由提供的 reference 識別的監視器。

如果 monitor_ref 是呼叫處理程序透過呼叫 monitor/1 取得的參考,則會關閉該監控。如果監控已關閉,則不會有任何動作。

有關更多資訊,請參閱 :erlang.demonitor/2

由編譯器內嵌。

@spec info(port()) :: keyword() | nil

傳回有關 port 的資訊(或在埠已關閉時傳回 nil)。

有關更多資訊,請參閱 :erlang.port_info/1

@spec info(port(), atom()) :: {atom(), term()} | nil

傳回 port 內特定欄位的資訊(或在埠已關閉時傳回 nil)。

有關更多資訊,請參閱 :erlang.port_info/2

@spec list() :: [port()]

傳回目前節點中所有埠的清單。

由編譯器內嵌。

連結至這個函數

monitor(port)

檢視原始碼 (自 1.6.0 起)
@spec monitor(port() | {name, node()} | name) :: reference() when name: atom()

開始從呼叫處理程序監控指定的 port

一旦受監控的埠處理程序死亡,訊息將以以下形式傳遞給監控處理程序

{:DOWN, ref, :port, object, reason}

其中

  • ref 是此函式傳回的監控參考;
  • object 是受監控的 port(以埠 ID 監控時)或 {name, node}(以埠名稱監控時);
  • reason 是退出原因。

有關更多資訊,請參閱 :erlang.monitor/2

由編譯器內嵌。

@spec open(name(), list()) :: port()

開啟一個埠,提供一個 name 元組和一個 options 清單。

上述模組文件包含受支援 name 值的文件和範例,摘要如下

  • {:spawn, command} - 執行外部程式。 command 必須包含程式名稱,並可選擇包含以空格分隔的引數清單。如果傳遞名稱中帶有空格的程式或引數,請使用下一個選項。
  • {:spawn_executable, filename} - 執行由絕對檔案名稱 filename 給定的可執行檔。可透過 :args 選項傳遞引數。
  • {:spawn_driver, command} - 產生所謂的埠驅動程式。
  • {:fd, fd_in, fd_out} - 存取由 VM 開啟的檔案描述子 fd_infd_out

有關更多資訊和選項清單,請參閱 :erlang.open_port/2

由編譯器內嵌。