2016-07-21 8 views
1

Ich habe eine GenServer, die Verbindung zu einer Remote-TCP-Verbindung über gen_tcp.Wie kann ich ein TCP-Timeout auf einem aktiven Socket in Elixir mit gen_tcp erkennen?

opts = [:binary, active: true, packet: :line] {:ok, socket} = :gen_tcp.connect('remote-url', 8000, opts}

Ich bin Umgang mit Nachrichten mit:

def handle_info({:tcp, socket, msg}, stage) do IO.inspect msg {:noreply, state} end

die großen Werke. Der TCP-Server ist jedoch anfällig für Zeitüberschreitungen. Wenn ich gen_tcp.recv verwende, könnte ich eine Zeitüberschreitung angeben. Ich verwende jedoch active: true, um Nachrichten mit handle_info zu empfangen, und muss nicht übergehen und recv aufrufen. So wartet die GenServer glücklich auf die nächste Nachricht, auch wenn der Server abgelaufen ist.

Wie kann ich die GenServer auslösen eine Funktion, wenn es nach X Sekunden keine Nachricht von der TCP-Verbindung erhalten hat? Bin ich mit recv stecken geblieben?

+0

Nicht sicher, warum jemand dies abgelehnt hat. Dies ist ein grundlegendes Rätsel, das viele Menschen schon früh mit Erlang, Elixir, LFE usw. bei Verwendung von '{active, true}' - Sockets konfrontieren - und die Lösung ist nicht nur auf Elixir anwendbar. – zxq9

Antwort

4

Wenn die TCP-Verbindung selbst abgelaufen ist, sollten Sie eine geschlossene Socket-Nachricht erhalten {tcp_closed, Socket}. Passen Sie darauf in handle_info und das ist es. Wie zur Einrichtung Ihre eigene Timeout auf der Verbindung, verwende ich normalerweise erlang:send_after/3, um mir eine Nachricht zu senden - effektiv eine Timeout-Nachricht Semantik hinzufügen, die ich in handle_info oder was auch immer receive Service-Schleife möglicherweise erhalten kann.

erlang:send_after(?IDLE_TIMEOUT, self(), {tcp_timeout, Socket})

Dies muss jedes Mal, Verkehr empfangen wird mit einer Aufhebung des Timer kombiniert werden. Dies könnte in etwa so aussehen:

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}. 
+0

Danke für die Antwort! Ich erkannte auch, dass die Unterstützung von Elixir 'GenServer einen 'Timeout'-Wert zurückliefert, also {: noreply, state, timeout}', was dazu führt, dass eine Timeout-Nachricht gesendet wird, wenn innerhalb von 'timeout' keine andere Nachricht empfangen wird. Ich bin mir nicht sicher, ob das in Erlang 'gen_servers' vorhanden ist oder ob Elixir es hinzufügt, aber das hat das Problem für mich gelöst. Ihre Lösung würde auch funktionieren, wenn ich keinen Zugriff auf diesen Timeout-Helfer hätte. Vielen Dank! – user2666425

+0

@ user2666425 Ja, Erlang gen_servers haben den genau gleichen Timeout-Wert, den Sie in den Rückgabewert eines 'handle_ *' -Aufrufs einbeziehen können. * ABER * dies ist ein Timeout _für irgendeine Nachricht an allen_, und das ist nicht dasselbe wie ein Timeout _für eine TCP-Nachricht_. Wenn Sie beispielsweise Ihren TCP-Handler mit Erlang-Systemmeldungen spammen, wird dieser Zeitüberschreitungswert ständig zurückgesetzt und die Tatsache, dass der Socket keine Aktivität ausgeführt hat, wird ignoriert. Aus diesem Grund füge ich für diese spezielle Verwendung meine eigene 'tcp_timeout'-Nachricht als eine Nachricht speziell für den Socket-Verkehr hinzu. – zxq9

+0

Das macht Sinn. Vielen Dank für die klaren und hilfreichen Antworten! – user2666425

Verwandte Themen