我正在使用Phoenix框架并尝试配置一个进程,该进程将连接到另一个为我提供一些feed的服务器。我正在使用Erlang的gen_tcp
来做到这一点。我希望稍后将这些Feed发送给与频道相关的客户(不是那么重要)。
我希望它受到监督,因此如果没有与远程资源的连接,应用程序就不会中断,而是等待连接恢复。如果有任何错误,我希望主管重启。
这是我试过的:
defmodule HelloPhoenix.TestServer do
use GenServer
def start_link() do
GenServer.start_link(__MODULE__, [])
end
def init(_) do
pid = spawn run
{:ok, pid}
end
def run do
socket = _connect()
_loop(socket)
end
defp _loop(socket) do
_recv(socket) |>
_handle_message(socket) |>
_broadcast
_loop socket
end
defp _connect do
opts = [:binary, packet: :line, active: false, reuseaddr: true, keepalive: true]
{:ok, socket} = :gen_tcp.connect('10.10.10.10', 1000, opts)
socket
end
defp _recv(socket) do
case :gen_tcp.recv(socket, 0) do
{:ok, m} -> m
{:error, _} -> raise :disconnected
end
end
defp _handle_message(message, _socket) do
# parse message and stuff like that
String.strip message
end
defp _broadcast(parsed_message) do
HelloPhoenix.Endpoint.broadcast("room:lobby", "receive", parsed_message)
end
end
我还将工作人员添加到应用程序主管。这有效,但是当我重新启动连接时,我会收到类似的错误(整个应用程序崩溃......):
=INFO REPORT==== 14-Mar-2016::23:53:54 ===
application: logger
exited: stopped
type: temporary
** (Mix) Could not start application hello_phoenix: HelloPhoenix.start(:normal, []) returned an error: shutdown: failed to start child: HelloPhoenix.TestServer
** (EXIT) an exception was raised:
** (RuntimeError) :disconnected
(hello_phoenix) lib/hello_phoenix/test_server.ex:83: HelloPhoenix.TestServer._handle_message/2
(hello_phoenix) lib/hello_phoenix/test_server.ex:46: HelloPhoenix.TestServer._loop/1
(hello_phoenix) lib/hello_phoenix/test_server.ex:43: HelloPhoenix.TestServer._loop/1
(hello_phoenix) lib/hello_phoenix/test_server.ex:34: HelloPhoenix.TestServer._run/0
(hello_phoenix) lib/hello_phoenix/test_server.ex:12: HelloPhoenix.TestServer.init/1
(stdlib) gen_server.erl:328: :gen_server.init_it/6
(stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
我应该如何解决这个问题,以免它会崩溃/等待重新连接?
答案 0 :(得分:1)
之前我已经做过类似的事情来读取串口。您不希望从init/1
回调进行连接。而是在进程启动后向进程发送消息以进行连接。您还希望在连接时处理错误,以便主管不重新启动此服务器。如果端口不可用,最终会导致整个应用程序崩溃。
使用您的代码我将以下示例放在一起。我没有编译或测试这个YMMV。希望这会有所帮助。
defmodule TcpUsage do
use GenServer
defmodule State do
defstruct socket: nil
end
def start_link, do: GenServer.start_link(__MODULE__, [])
def init(_) do
# try to open connection in 100ms, this could be a send or cast
Process.send_after(self, :connect, 100)
{:ok, %State{}}
end
def handle_info(:connect, %State{socket: nil} = state) do
{:noreply, connect(state)}
end
def handle_call({:recv, bytes, timeout}, _, state) do
case :gen_tcp.recv(state.socket, bytes, timeout) do
{:ok, _} = ok ->
{:reply, ok, state}
{:error, :timeout} = timeout ->
{:reply, timeout, state}
{:error, _} = error ->
{:reply, error, state}
end
end
defp connect(state) do
opts = [:binary, packet: :line, active: false, reuseaddr: true, keepalive: true]
# I would try to move these to your config, and pass them as args into the genserver,
# Or load them from here with Application.get_env
case :gen_tcp.connect('10.10.10.10', 1000, opts) do
{:ok, socket} ->
%{state| socket: socket}
{:error, _} ->
Process.send_after(self(), :connect, 5_000) # maybe do some backoff here, get the wait time from a function
state
end
end
end
如果套接字关闭以启动重新连接循环,您可能希望发送新的:connect
消息。
您可能还希望在此服务器的terminate
回调中处理关闭套接字。
此模块https://github.com/fishcakez/connection也可能会有所帮助。