Wie prüfen, ob eine TCP-Verbindung noch steht?

Begonnen von Dr. Boris Neubert, 04 März 2014, 19:00:04

Vorheriges Thema - Nächstes Thema

Dr. Boris Neubert

Zitat von: ntruchsess am 10 März 2014, 23:06:18
Das betreffende Modul braucht eine ReadFn, die aus der Fhem-mainloop aufgerufen wird, wenn auf der Socketverbindung Daten eingetroffen sind. Dazu noch eine Funktion, die prüfen kann, ob der gewünschte Timeout abgelaufen ist. Anstelle eines Aufrufs von 'Set/get ...' mit Warten auf die Antwort schickt das betreffende Modul einfach den Request raus, setzt ein 'timeoutflag' und startet den InternalTimer mit der TimeoutFunktion. Anschließend tut es erst mal nichts mehr. Kommen Daten über die ReadFn rein, werden die ausgewertet, die Readings geupdatet und das TimeoutFlag gelöscht. Wird vorher die Timeoutfunktion aufgerufen, schreibt diese etwas dazu passendes in die Readings. Reagieren tut man auf das Ganze, indem man die passenden Notifys auf die gewünschten Readings setzt. (Eines für erfolgreichen Empfang der Antwort, eines für Timeout).

OK, das verstehe ich. Im Prinzip schießt man ein Kommando an das Gerät ab und tut dann so, als ob die darauf vom Gerät erstellte Antwort spontan gesendet wurde (wie bei einer Wetterstation, die ab und an ungefragt die Temperatur schickt). Zusätzlich überwacht man über einen Timer, ob wirklich was zurückkam, und geht in eine Fehlerbehandlung, wenn das nicht innerhalb der festgelegten Zeit geschah. Das ist clever.

Ich beschäftige mich aber mit einer viel trivialeren Frage: ich will eine Lampe mit set Lampe on anknipsen. Und dann soll die Lampe angehen und ich kann das nächste Kommando senden. Und wenn die Verbindung zum Schalter kaputt ist, soll diese erst noch versucht werden, neu aufzubauen. Und wenn das alles nicht klappt, dann bekomme ich eine Fehlermeldung.

Also doch meine (nur im Fehlerfall maximal 2x Timeout) blockierende DevIo_Expect aus einem vorigen Post von mir in diesem Thread? In der Regel antwortet so ein Mikrocontroller ja innerhalb einer Sekunde und der Timeout kann kurz eingestellt sein. Ist das eigentlich wirklich problematisch, zumal der Fall ja nur eintritt, wenn die Verbindung zum Gerät abgerissen ist?

Ich würde dann aber dafür sorgen, daß Geräte, die DISCONNECTED sind, überhaupt erst nicht angesprochen werden. DISCONNECTED ist ein Gerät dann, wenn DevIo_Expect trotz reopen und erneuter Versendung innerhalb des Timeouts keine Antwort zurückliefert. Es braucht dann wohl noch einen Zwischenzustand FAILING.

Viele Grüße
Boris

Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

ntruchsess

Zitat von: Dr. Boris Neubert am 11 März 2014, 19:51:12Ist das eigentlich wirklich problematisch, zumal der Fall ja nur eintritt, wenn die Verbindung zum Gerät abgerissen ist?

Wenn der Timeout akzeptabel kurz, das Kommando im Regelfall schnell erledigt und der Fehlerfall eher selten ist, ist es natürlich nicht problematisch. Im Allgemeinen Fall, bei dem die 3 Kriterien nicht (sicher) zutreffen, aber schon.
while (!asleep()) {sheep++};

Dr. Boris Neubert

#17
So, jetzt habe ich zwei Funktionen in DevIo.pm nachgerüstet.

DevIo_SimpleReadWithTimeout() liest Daten vom Gerät, wartet aber höchstens bis zum Timeout, daß Daten vom Gerät zum Lesen bereitgestellt werden.


########################
# wait at most timeout seconds until the file handle gets ready
# for reading; returns undef on timeout
sub
DevIo_SimpleReadWithTimeout($$)
{
  my ($hash, $timeout) = @_;

  my $rin = "";
  vec($rin, $hash->{FD}, 1) = 1;
  my $nfound = select($rin, undef, undef, $timeout);
  return DevIo_DoSimpleRead($hash) if($nfound> 0);
  return undef;
}


DevIo_Expect() sendet eine Nachricht an das Gerät. Dann wird die Antwort vom Gerät gelesen und zurückgeliefert. Kommt es zum Timeout, wird die Verbindung zum Gerät geschlossen und neu aufgebaut. Danach wird die Nachricht erneut an das Gerät gesendet, die Antwort vom Gerät gelesen und zurückgeliefert. Scheitert auch dies, wird die Verbindung zum Gerät endgültig geschlossen. Das Gerät ist dann in einem Fehlerzustand.


########################
# Write something, then read something
# reopen device if timeout occurs and write again, then read again
sub DevIo_Expect($$$)
{
  my ($hash, $msg, $timeout) = @_;
  my $name= $hash->{NAME};
 
  my $state= $hash->{STATE};
  if($state ne "opened") {
    Log3 $name, 2, "Attempt to write to $state device.";
    return undef;
  }
  # write something
  return undef unless defined(DevIo_SimpleWrite($hash, $msg, 0));
  # read answer
  my $answer= DevIo_SimpleReadWithTimeout($hash, $timeout);
  return $answer unless($answer eq "");
    # the device has failed to deliver a result
  $hash->{STATE}= "failed";
  DoTrigger($name, "FAILED");
  # reopen device
  # unclear how to know whether the following succeeded
  Log3 $name, 2, "$name: first attempt to read timed out, trying to close and open the device.";
  # The next two lines are required to avoid a deadlock when the remote end closes the connection
  # upon DevIo_OpenDev, as e.g.    netcat -l <port>      does.
  DevIo_CloseDev($hash);
  sleep(5) if($hash->{USBDEV});
  DevIo_OpenDev($hash, 0, undef); # where to get the initfn from?
  # write something again
  return undef unless defined(DevIo_SimpleWrite($hash, $msg, 0));
  # read answer again
  $answer= DevIo_SimpleReadWithTimeout($hash, $timeout);
  # success
  if($answer ne "") {
    $hash->{STATE}= "opened"; 
    DoTrigger($name, "CONNECTED");
    return $answer;
  }
  # ultimate failure
  Log3 $name, 2, "$name: second attempt to read timed out, this is an unrecoverable error.";
  DoTrigger($name, "DISCONNECTED");
  return undef; # undef means ultimate failure
}


Unschön ist die Erfordernis zum Schließen im Mittelteil, da ein einfaches Reopen zum Deadlock führen kann.

Ich habe DevIo_Expect() mit ECMD intensiv getestet, und zwar mit der NetServer- und der Ethersex-Firmware auf einem AVRNETIO und mit einem netcat -l <port> auf der Gegenseite.

Ich bitte um Review und idealerweise Einbau in DevIO.pm. Patch anbei (Achtung: im Patch ist das Logging in DevIo_SimpleWrite() mit einer Begründung auskommentiert).

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

rudolfkoenig

Habs eingecheckt, bis auf den Ausbau der Log-Zeile. Falls es nicht geloggt werden soll, muss das Modul temporaer das Logging abschalten (verbose fuer das Geraet auf 0 setzen)

Dr. Boris Neubert

Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Markus Bloch

Währe es nicht auch gut für TCP Verbindungen Keep-Alives zu aktivieren um eine höhere Chance zu erreichen TCP Verbindungen als geschlossen zu erkennen, wo die Gegenseite aufgrund eines Stromausfalls o.ä. die Session nicht mehr kennt?.



DevIo.pm - Zeile 146:  $conn->setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if(defined($conn));



Gruß
Markus
Developer für Module: YAMAHA_AVR, YAMAHA_BD, FB_CALLMONITOR, FB_CALLLIST, PRESENCE, Pushsafer, LGTV_IP12, version

aktives Mitglied des FHEM e.V. (Technik)

rudolfkoenig

Habs hinzugefuegt, auch zu TcpServerUtils.pm.

Scheint keine negativen Auswirkungen zu haben, habs aber nicht getestet, wie es im Fehlerfall auswirkt.