author: Sophie DeBenedetto author_link: https://github.com/sophiedebenedetto categories: til date: 2019-04-17 layout: post title: TIL Using Erlang Ports excerpt: >

Use Ports and GenServers to communicate from your Elixir app to processes running outside the Erlang VM.

TIL 使用 Erlang 端口

Erlang 的端口为我们提供了一个通过发送和接收消息与外部进程进行通信的接口。Elixir 的 Port 模块是建立在 Erlang 端口之上的,它可以轻松地启动和管理操作系统进程。

通过 open/2 函数可以创建一个端口来执行某个操作系统进程。

  1. cmd = "echo hello"
  2. Port.open({:spawn, cmd}, [:binary])
  3. # => #Port<0.5>

Here, we pass open/2 the :spawn tuple that contains the binary we want to execute over our port. The code above will execute echo hello on our OS for us.

在这里,我们给 open/2 传递了一个 :spawn 元组,其中包含了我们要在我们的 port 上执行的二进制文件。上面的代码将在我们的操作系统上执行 “echo hello”。

所以,为什么这是一个很有用的工具呢?

不难想象,你可能有一个程序需要执行一些 Elixir 并不擅长的功能,或者你已经有了一个用其他语言编写的脚本。比方说,我们的 Elixir 应用程序需要监听特定目录中的变化,并通过执行一些代码来做出响应。我们想利用fswatch来监听和报告这些变化。我们可以借助 ports!

创建一个进程并且监听消息

我们将使用一个端口来创建 fswatch 进程。打开端口的 Elixir 进程是该端口的所有者,并将接收来自该端口的消息。当通过该端口运行的进程向 STDOUT 发送任何信息时,消息就会从该端口发送给所有者(即 Elixir)。

我们将定义一个 FsWatchAdapter 模块 ,以打开我们的端口并从它那里接收消息。我们的模块将使用 GenServer,这样它就可以接收来自端口的消息并对其采取响应。

  1. defmodule FsWatchAdapter do
  2. use GenServer
  3. def start_link(dir) do
  4. GenServer.start_link(__MODULE__, dir)
  5. end
  6. def init(dir) do
  7. state = %{
  8. port: nil,
  9. dir: dir
  10. }
  11. {:ok, state, {:continue, :start_fswatch}}
  12. end
  13. def handle_continue(:start_fswatch, state = %{dir: dir}) do
  14. cmd = "fswatch #{dir}"
  15. port = Port.open({:spawn, cmd}, [:binary, :exit_status])
  16. state = Map.put(state, :port, port)
  17. {:noreply, state}
  18. end
  19. def handle_info({port, {:data, msg}}, state) do
  20. IO.puts "Received message from port: #{msg}"
  21. {:noreply, state}
  22. end
  23. end

这里我们用一个我们想监听的目录作为参数来启动 GenServer。我们使用handle_continue/2函数在一个端口上启动 fswatch。然后我们将端口存储在 GenServer 的状态中,以便以后使用。

最后,我们定义了一个 handle_info/2 函数,它知道当 fswatch 进程把一些东西放到 STDOUT 时,如何响应 GenServer 进程从端口收到的消息。

让我们看看我们的代码执行!你可以通过以下方式进行测试

  • 复制粘贴这个模块到 iex 控制台.
  • 在 iex 里面:
  1. iex> FsWatchAdapter.start_link("~/Desktop")
  • 在你的桌面创建一个 “testing-ports.txt” 文件
  • 你可以在 iex 控制台看到:
  1. iex> Received message from port: "/Desktop/testing-ports.txt"

为了终止 fswatch 进程,我们只需要终止 GenServer 进程即可。由于我们的 FsWatchAdapter 是端口所有者,终止它将终止在打开的端口中执行的进程。

总结

端口是在 Elixir 代码和任何外部进程之间传递消息的便捷方式。通过利用 GenServers,我们可以构建一种通信机制,使我们的应用程序能够发送、接收和响应外部进程的消息。您可以在这里了解更多关于 Elixir 端口的信息,也可以在这里了解更多关于 Erlang 端口的信息。