如何使用gen_tcp在Elixir的活动套接字上检测TCP超时?
我有一个GenServer
通过gen_tcp
连接到远程TCP连接。如何使用gen_tcp在Elixir的活动套接字上检测TCP超时?
opts = [:binary, active: true, packet: :line] {:ok, socket} = :gen_tcp.connect('remote-url', 8000, opts}
我处理消息有:
def handle_info({:tcp, socket, msg}, stage) do IO.inspect msg {:noreply, state} end
伟大的工程。但是,TCP服务器很容易超时。如果我使用gen_tcp.recv
,我可以指定超时。但是,我正在使用active: true
来接收带有handle_info
的消息,而不必循环并呼叫recv
。所以,GenServer
高兴地等待下一条消息,即使服务器超时。
如何让GenServer
在X秒后没有收到来自TCP连接的消息时触发一个函数?我坚持使用recv
?
如果TCP连接本身已超时,那么您应该收到一封闭的套接字消息{tcp_closed, Socket}
。与handle_info
匹配就是这样。至于在连接上建立你自己的超时,我通常用erlang:send_after/3
给自己发送一条消息 - 有效地添加一个超时消息语义,我可以在handle_info
或任何可能存在的receive
服务循环中收到。
erlang:send_after(?IDLE_TIMEOUT, self(), {tcp_timeout, Socket})
这必须是每次接收到交通定时器的取消配对。这可能看起来像这样:
handle_info({tcp, Socket, Bin}, State = #s{timer = T, socket = Socket}) ->
_ = erlang:cancel_timer(T),
{Messages, NewState} = interpret(Bin, State),
ok = handle(Messages),
NewT = erlang:send_after(?IDLE_TIMEOUT, self(), {tcp_timeout, Socket})
{noreply, NewState#s{timer = NewT}};
handle_info({tcp_closed, Socket}, State = #s{timer = T, socket = Socket}) ->
_ = erlang:cancel_timer(T),
NewState = handle_close(State),
{noreply, NewState};
handle_info({tcp_timeout, Socket}, State = #s{socket = Socket}) ->
NewState = handle_timeout(State),
{noreply, NewState};
handle_info(Unexpected, State) ->
ok = log(warning, "Received unexpected message: ~tp", [Unexpected]),
{noreply, State}.
感谢您的回答!我也意识到,Elixir'GenServer's支持返回'timeout'值,所以{:noreply,state,timeout}'导致发送超时消息,如果在timeout内没有收到其他消息。我不确定Erlang是否存在于gen_servers中,或者Elixir是否将它添加进去,但是这为我解决了问题。如果我无法访问该超时助手,您的解决方案也可以工作。谢谢! – user2666425
@ user2666425是的,Erlang gen_servers具有完全相同的超时值,您可以将其包含在来自'handle_ *'调用的返回值中。 *但是*对于all_中的任何消息,这是一个超时_,并且这与TCP消息_的超时_不同。例如,如果你使用Erlang系统消息给你的TCP处理程序发送垃圾邮件,它将不断重置该超时值并且错过了套接字没有活动的事实。这就是为什么这个特殊的用途,我添加自己的'tcp_timeout'消息作为特定于套接字流量的消息。 – zxq9
这很有道理。非常感谢您提供清晰有用的答案! – user2666425
不知道为什么有人低估了这一点。这是许多人很早就用Erlang,Elixir,LFE等人使用'{active,true}'套接字所面临的一个基本难题 - 而且这个解决方案不仅适用于Elixir。 – zxq9