You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Elixir中如何让spawn启动的TCP连接进程持续运行或自动重启?

解决Elixir中spawn进程崩溃后自动重启的问题

你的代码当前存在两个核心问题导致进程崩溃后无法自动恢复:

  1. 虽然通过Process.monitor监控了spawn的进程,但GenServer没有处理监控产生的DOWN消息,无法触发重启逻辑;
  2. connect函数仅处理了:gen_tcp.connect的错误返回,若receive_news_feed_helper抛出异常,进程会直接崩溃且无重启机制。

以下是两种可行的解决方案,推荐使用第二种(监督树),这是Elixir/OTP的惯用容错方式。


方案一:在GenServer中处理监控消息重启进程

修改现有GenServer,添加对DOWN消息的处理,同时增强connect函数的异常捕获能力:

defmodule YourGenServer do
  use GenServer

  @server_ip "your_server_ip"
  @port 1234
  @reconnect_delay 5000 # 避免频繁重试的延迟

  def start_link(arg) do
    GenServer.start_link(__MODULE__, arg)
  end

  def init(arg) do
    pid = spawn(fn -> connect() end)
    ref = Process.monitor(pid)
    # 将监控引用存入状态,确保只响应当前进程的DOWN消息
    {:ok, %{arg: arg, monitor_ref: ref}}
  end

  def connect() do
    case :gen_tcp.connect(@server_ip, @port, [:binary, {:active, false}]) do
      {:ok, tcp_socket} ->
        send_initial_commands(tcp_socket)
        try do
          receive_news_feed_helper(tcp_socket)
        rescue
          e ->
            IO.puts("接收新闻消息时异常:#{inspect(e)}")
            :gen_tcp.close(tcp_socket)
            connect() # 异常后重启连接
        after
          :gen_tcp.close(tcp_socket)
        end

      {:error, reason} ->
        IO.puts("连接失败:#{inspect(reason)},#{@reconnect_delay}ms后重试")
        Process.sleep(@reconnect_delay)
        connect()
    end
  end

  defp send_initial_commands(socket) do
    :ok = :gen_tcp.send(socket, "S,SET PROTOCOL,6.2\r\n")
    :ok = :gen_tcp.send(socket, "S,NEWSON\r\n")
  end

  # 处理监控进程崩溃的DOWN消息
  def handle_info({:DOWN, ref, :process, _pid, reason}, %{monitor_ref: ref} = state) do
    IO.puts("连接进程崩溃,原因:#{inspect(reason)},正在重启")
    pid = spawn(fn -> connect() end)
    new_ref = Process.monitor(pid)
    {:noreply, %{state | monitor_ref: new_ref}}
  end

  # 忽略其他未知消息
  def handle_info(_msg, state) do
    {:noreply, state}
  end

  # 实现你的receive_news_feed_helper逻辑
  defp receive_news_feed_helper(socket) do
    case :gen_tcp.recv(socket, 0) do
      {:ok, data} ->
        # 处理收到的新闻数据
        IO.puts("收到新闻:#{data}")
        receive_news_feed_helper(socket)
      {:error, _reason} ->
        :ok
    end
  end
end

方案二:使用监督树管理连接进程(推荐)

利用Elixir的OTP监督机制,将连接逻辑封装为独立的Worker进程,由Supervisor自动处理重启:

1. 创建新闻Feed Worker模块

defmodule NewsFeedWorker do
  use GenServer

  @server_ip "your_server_ip"
  @port 1234
  @reconnect_delay 5000

  def start_link(_arg) do
    GenServer.start_link(__MODULE__, nil)
  end

  def init(_arg) do
    # 启动时触发连接逻辑
    send(self(), :connect)
    {:ok, nil}
  end

  def handle_info(:connect, state) do
    case :gen_tcp.connect(@server_ip, @port, [:binary, {:active, false}]) do
      {:ok, tcp_socket} ->
        send_initial_commands(tcp_socket)
        # 进入消息接收循环
        receive_loop(tcp_socket)
        # 循环退出后触发重连
        send(self(), :connect)
        {:noreply, state}

      {:error, reason} ->
        IO.puts("连接失败:#{inspect(reason)},#{@reconnect_delay}ms后重试")
        Process.send_after(self(), :connect, @reconnect_delay)
        {:noreply, state}
    end
  end

  defp receive_loop(socket) do
    case :gen_tcp.recv(socket, 0) do
      {:ok, data} ->
        process_news_data(data)
        receive_loop(socket)
      {:error, reason} ->
        IO.puts("接收数据失败:#{inspect(reason)}")
        :gen_tcp.close(socket)
    end
  end

  defp send_initial_commands(socket) do
    :ok = :gen_tcp.send(socket, "S,SET PROTOCOL,6.2\r\n")
    :ok = :gen_tcp.send(socket, "S,NEWSON\r\n")
  end

  defp process_news_data(data) do
    # 替换为你的数据处理逻辑
    IO.puts("处理新闻数据:#{data}")
  end

  # 忽略其他消息
  def handle_info(_msg, state) do
    {:noreply, state}
  end
end

2. 将Worker加入应用监督树

修改你的应用启动模块,将Worker添加到子进程列表:

defmodule YourApp.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [
      # 其他子进程...
      {NewsFeedWorker, []}
    ]

    # 使用:one_for_one策略,单个子进程崩溃时仅重启该进程
    Supervisor.start_link(children, strategy: :one_for_one, name: YourApp.Supervisor)
  end
end

关键说明

  • Supervisor默认的重启策略是:permanent,意味着Worker进程无论因何崩溃都会被自动重启;
  • 若需要调整重启行为(如仅在异常时重启),可在启动Worker时指定restart: :transient;
  • 使用Process.send_after替代Process.sleep,避免阻塞GenServer的消息循环。

内容的提问来源于stack exchange,提问作者waheed-fullstack-engineer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 16:47:03