通过基于代理的 IP 地址列表循环工作人员

Looping workers trough an Agent-based list of IP addresses

我正在研究基于 Elixir 的网络扫描仪。 我的目标是能够将 IP 地址列表存储在代理中(简单),并在该代理上循环工作人员,以便他们获取列表的头部并启动 scanning/probing 函数(不太容易)。 虽然这听起来像是某种理论上的东西,但如果有帮助,我可以提供代码片段。

编辑:显然我设法弄清楚了该怎么做(我想这就是这样做的方式)。

defmodule Find3r.Worker do
use GenServer
require Logger

  def start_link, do: GenServer.start_link(__MODULE__, :ok, name: :worker)

  def init(:ok) do
    Logger.debug("Worker activated.")
    {:ok, :ok}
  end

  def scan_range(range), do: GenServer.call(__MODULE__, {:scan_range, range})

  def handle_call({:scan_range, range}, _from, :ok) do
    Logger.debug("Scanning #{range}")
    {:ok, pid} = Agent.start(Find3r.Utils, :addresses_for, [range])
    Agent.get_and_update(pid, fn(state) -> foo(state) end) |> loop(pid)
    {:reply, :ok, :ok}
  end


  # Avoids an exception when the Agent runs out of addresses
  defp foo([ip|rest]), do: {ip, rest}
  defp foo(_),         do: {[], []}

  defp loop(ip, pid) when is_tuple(ip) do
    spawn(Find3r.Utils, :scan, [ip])
    Agent.get_and_update(pid, fn(state) -> foo(state) end) |> loop(pid)
  end
  defp loop(_, _), do: nil


end

项目已移至GitHub
再次感谢 o/

一种简单的(hack-ish)方法来完成[我认为]您所描述的事情:

defmodule MultiScanner do
  def scan(range) do
    main_proc = self
    Enum.each(range, fn(ip) -> 
                       spawn_link(fn -> 
                         res = Find3r.Utils.scan(ip)
                         send main_proc, {:ip_results, ip, res}
                       end)
                     end)
    ip_map = Enum.map(range, fn(ip) -> {ip, nil} end)
             |> Enum.into(%{})
    wait_for_results(ip_map)
  end

  defp wait_for_results(ip_map, acc \ %{})
  defp wait_for_results(ip_map, acc) when map_size(ip_map) == 0, do: acc
  defp wait_for_results(ip_map, acc) do
    receive do
      {:ip_results, ip, res} ->
        acc = Map.put(acc, ip, res)
        ip_map = Map.delete(ip_map, ip)
        wait_for_results(ip_map, acc)
    end
  end
end

这会为每个 IP 地址生成一个进程,该进程运行扫描,并将结果发送回调用进程,调用进程又将结果折叠为 ip => 结果的映射。

这种方法显然存在很多问题 - 最明显的是您可能不希望所有扫描同时开始。

有很多方法可以解决这个问题,但除了像评论中建议的 poolboy:gproc 这样的固定解决方案——如果我要实际实施对于这种方法的稳健解决方案,我可能会把这样的监督树放在一起:

Scan.Supervisor
  -ScanWorker.Supervisor (simple-one-for-one)
      -ScanWorker (anon GenServer, executing a single scan)
      -ScanWorker 
      ...
  -ScanWorker.Manager (Named GenServer, coordinates the ScanWorkers)

我的信封架构的背面有上面的 4 个模块,加上一个顶层模块 Scan 来容纳 public API。我还认为需要一个小时左右的时间来充实它,以至于我不会不好意思向同事展示它。

我开始为您勾勒出这个轮廓 - 但坦率地说,您的问题含糊不清且写得不好。你似乎没有花太多时间写它,甚至懒得校对它。我在这里整理的答案可能比你的问题应得的更慷慨:我认为如果你花更多的时间和精力来写你的问题,你会在 SO 上找到更好的成功。

我设法想出了如何做到这一点(我想这就是人们这样做的方式)。

defmodule Find3r.Worker do
use GenServer
require Logger

  def start_link, do: GenServer.start_link(__MODULE__, :ok, name: :worker)

  def init(:ok) do
    Logger.debug("Worker activated.")
    {:ok, :ok}
  end

  def scan_range(range), do: GenServer.call(__MODULE__, {:scan_range, range})

  def handle_call({:scan_range, range}, _from, :ok) do
    Logger.debug("Scanning #{range}")
    {:ok, pid} = Agent.start(Find3r.Utils, :addresses_for, [range])
    Agent.get_and_update(pid, fn(state) -> foo(state) end) |> loop(pid)
    {:reply, :ok, :ok}
  end


  # Avoids an exception when the Agent runs out of addresses
  defp foo([ip|rest]), do: {ip, rest}
  defp foo(_),         do: {[], []}

  defp loop(ip, pid) when is_tuple(ip) do
    spawn(Find3r.Utils, :scan, [ip])
    Agent.get_and_update(pid, fn(state) -> foo(state) end) |> loop(pid)
  end
  defp loop(_, _), do: nil


end