Schrift
Wiki:Tipp zum Debugging: use Data::Dumper; local $Data::Dumper::Useqq = 1; print Dumper \@var;
[thread]7116[/thread]

Ausgabe vom Server wird nicht richtig abgeholt



<< >> 10 Einträge, 1 Seite
Echelon1010000
 2005-07-08 12:34
#56071 #56071
User since
2005-06-29
18 Artikel
BenutzerIn
[default_avatar]
Hi
Ich hab hier ein kleines Problem mit dem Abholen von Antworten eines TCP Servers. Der Server gehört zu "lcdproc" einer Anwendung zum Darstellen von Informationen auf einem LCD. Eine Anwendung kann sich via TCP mit dem Server verbinden und über ein Klartextprotokoll Daten übermitteln. Diese werden mit einfachen Befehlen übermittelt die dem Server sagen was er wie und wo auf dem LCD darstellen soll.

Der Server unterstützt mehrere sogenannte Screens. Diese Screens repräsentieren eine Displayanzeige und werden in regelmässigen Abständen gewechselt. So ist es zum Beispiel möglich abwechselnd eine Anzeige für die CPU Temperatur und die Lüfterdrehzahl darzustellen.

Immer wenn der Server zum nächsten Screen übergeht gibt er eine Meldung an alle verbundenen Clients aus die allen Clients mitteilt wann welcher Screen angezeigt wird. Diese Mtteilung erscheint als Information auf dem Ausgabekanal des Servers und sieht so aus (Angenommen ich hab den Screen "CPU" und den Screen "Luefter":
Code: (dl )
1
2
3
 
ignore CPU
listen Luefter


Auf einen erfolgreichen Befehl gibt der Server die Meldung "success" aus, ein fehelerhafter Befehl wird mit der Meldung "Huh? $Fehlermeldung" kommentiert.

Ich habe nun mit Perl eine Anwendung geschrieben die Systemdaten einsammelt und auf diesen Screens anzeigt. Das klappt auch eigentlich sehr gut. Bis auf einen kleinen Fehler: Ich habe meine Anwendung so geschrieben das die Informationen über die angezeigten Screens verworfen werden. Informationen über Erfolg/Fehler werden ausgwertet und entsprechend verarbeitet. Die Infonachrichten verwerfe ich weil ich eine Art Echtzeit Anzeige haben möchte und beim Wechsel auf einen neuen Screen nicht die Werte von vor 30 Sekunden da stehen haben will. Die Anzeigen lassen sich auch aktualisieren wenn der Screen im Moment nicht angezeigt wird. So halte ich meine Anzeigen immer aktuell.

Wenn also ein Befehl an das LCD abgesetzt wird holt die Anwendung die Rückmeldung vom Server ab. Dann wird geprüft ob es sich um eine Antwort handelt oder um eine Infonachricht. Falls es eine Infonachricht ist wird diese verworfen und die nächste Nachricht abgeholt. Falls es eine "success" oder "huh?" Meldung ist wird entsprechend ein return-Wert ausgegeben.

Leider ist es so das die Routine manchmal zwei Nachrichten abholt. Das sieht dann zum Beispiel so aus das die erhaltene Nachricht nicht "listen CPU" lautet sondern
Code: (dl )
1
2
listen CPU
success


Wie man sieht ist es eine Nachricht die sowohl eine Infonachricht enthält als auch eine angehängte Erfolgsmeldung.
Die Routine wertet jedoch die Nachricht als eine einzige Info Nachricht und springt zurück um die nächste Nachricht abzuholen. Da kommt nur nichts mehr, die Routine wartet ewig auf die nächste Nachricht, in der Folge hängt das Programm an dieser Stelle.

Meine Frage:
Wie kann ich das unterbinden das zwei Nachrichten abgeholt werden? Ich möchte als statt der gesamten Ausgabe des Servers immer nur eine Nachricht abholen und diese dann auswerten. Die einzelnen Nachrichten sind so wie es aussieht durch ein "Newline ( \n )" getrennt, gibt es da eine andere Möglichkeit diese abzuholen?

Nachfolgend der fragliche Code. Zuerst bispielhaft zwei der Routinen die auf das Display schreiben:
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
sub addstring
{
   # add a string to a screen
   my $screenname=shift;
   my $widgetname=shift;
   my $sock=getsocket();
   debug ("widget_add \"$screenname\" \"$widgetname\" string");
   print $sock "widget_add \"$screenname\" \"$widgetname\" string\n";
   my $answer=suck($sock);
   if ( $answer ne "1" ) { die "error while creating widget \"$widgetname\" in screen \"$screenname\". Server said: $answer";}
   return 1;
}

sub setstring
{
   # modify a string-type widget
   my $screenname=shift;
   my $widgetname=shift;
   my $x=shift;
   my $y=shift;
   my $value=shift;
   my $sock=getsocket();
   debug ("widget_set \"$screenname\" \"$widgetname\" $x $y \"$value\"");
   print $sock "widget_set \"$screenname\" \"$widgetname\" $x $y \"$value\"\n";
   my $answer=suck($sock);
   if ( $answer ne "1" ) { die "error while modifying widget \"$widgetname\" in screen \"screenname\". Server said: $answer";}
   return 1;
}


Alle anderen Routinen sehen genauso aus, nur die Befehle die an das Display übermittelt werden sind unterschiedlich. Die sub "getsocket();" ermittelt den passenden Socket auf den geschrieben werden muss da ich mit mehreren Sockets arbeite.

Nun die Sub die die Antworten abholt und auswertet:
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sub suck
{
   # read any message from the LCDd,
   # discard listen / ignore messages and return
   # value indicating a successful or failed operation


   my $sock=shift;
   my $message="";
   my $answer="";
   SUCKLOOP:
   # We need a point to jump back when we recieve a listen/ignore message
   recv ( $sock,$message,512,0); # 512 Bytes enough?
   chomp ($message);
   if ( index($message,"listen") >=0 ) { debug ("Discarded listen message ($message)");goto SUCKLOOP;}
   if ( index($message,"ignore") >=0 ) { debug ("Discarded ignore message ($message)");goto SUCKLOOP;}
   if ( index($message,"connect") >=0 ) { debug ("successfully registered as a client ($message)");return 1;}
   if ( index($message,"huh?") >=0 ) { debug("error ($message)");return $message;}
   if ( index($message,"success") >=0 ) { debug("success");return 1;}


   return 1;
}


Vielleicht kann mir da jemand helfen?

Gruss, Echelon
ptk
 2005-07-08 12:58
#56072 #56072
User since
2003-11-28
3645 Artikel
ModeratorIn
[default_avatar]
Du scheinst immer 512 Bytes auf einmal zu holen. Vielleicht solltest du lieber zeilenweise oder byteweise lesen und bei einem "\n" aufhoeren?
esskar
 2005-07-08 12:58
#56073 #56073
User since
2003-08-04
7321 Artikel
ModeratorIn

user image
hi...
dein problem ist, dass du 512 bytes lesen willst...
und wenn der server 2 zeilen schickt, passen die eben in die 512 bytes rein...
versuch mal folgendes (ich hoffe mal, dass du IO::Socket benutzt)

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sub suck {
  # read any message from the LCDd,
  # discard listen / ignore messages and return
  # value indicating a successful or failed operation


  my $sock=shift;
  my $answer="";
  my $sucking = 1;

  my $retval;
  while($sucking) {
     my $message = $sock->getline();
     chomp ($message);
     if ( index($message,"listen") >=0 ) { debug ("Discarded listen message ($message)");}
     elsif ( index($message,"ignore") >=0 ) { debug ("Discarded ignore message ($message)");}
     elsif ( index($message,"connect") >=0 ) { debug ("successfully registered as a client ($message)"); $retval = 1; $sucking = 0;}
     elsif ( index($message,"huh?") >=0 ) { debug("error ($message)"); $retval = $message; $sucking = 0;}
     elsif ( index($message,"success") >=0 ) { debug("success"); $retval = 1; $sucking = 0;}
  }

  return $retval
}


was ich nicht gut finde ist, dass du ab und an 1 zurück gibst, und ab und an einen String... schick ist das nicht
Echelon1010000
 2005-07-08 13:16
#56074 #56074
User since
2005-06-29
18 Artikel
BenutzerIn
[default_avatar]
Hi

Danke für deine Tip :) Ich hatte sowas vermutet, wusste aber nicht das er die 512 auch zeilenübergreifend liest. Und, was noch wichtiger ist: Ich wusste nicht wie ich sonst lesen soll.

Ich hab den Vorschlag mit "getline();" übernommen und teste es jetzt. Sobald ich Bescheid weiss geb ich Rückmeldung, wird aber eine Weile dauern, der Fehler tritt immer erst nach einer ganzen Weile auf, eben wenn zufällig zwei Nachrichten warten und auch so abgeholt werden wie oben beschrieben. Das Programm läuft jetzt erstmal testweise.

Zu den Rückgabewerten: Ich hatte ursprünglich nur Zahlen ( 1 und 0 ) zurückgegeben und keine grossartige Fehlerbehandlung eingebaut. Das kam später erst als das Programm vom kleinen Script zum Projekt "gewachsen" ist. Zur Fehlerkontrolle reicht es im Prinzip auch, einen String muss ich ja nur zurückgeben wenn es etwas zu melden gibt (Fehlermeldung).  Ich könnte natürlich return 1; zu return "1"; machen hielt das bis jetzt aber nicht für sinnvoll. Wahrscheinlich werde ich aber unter dem Aspekt "sauberer Code" dazu übergehen jeweils die Antwort zu übergeben.

Das Problem ist hier das es etwa zwanzig subs gibt die von rund 10 verschiedenen Modulen aufgerufen werden die sich jeweils in einem eigenen Childprozess befinden. Für jedes Modul existiert ein eigener Socket damit die Module ihre Daten möglichst schnell absetzen können und sich beim Schreiben und Antwortlesen nicht in die Quere kommen. Ich kann also die Fehlerbehandlung nicht direkt in dieser Sub lösen.
Zu den Modulen und Sockets vergleiche auch mein Post von letzter oder vorletzte Woche "Sockets verwalten". Eine Rückgabe von "1" für erfolgreiche Ausführung ist IMO zur Zeit im Entwicklungsstadium aber ausreichend.

Danke erstmal dazu :)

Gruss, Echelon
esskar
 2005-07-08 14:45
#56075 #56075
User since
2003-08-04
7321 Artikel
ModeratorIn

user image
hätt ja sowas vorgeschlagen

Code: (dl )
1
2
3
4
5
6
7
sub foo {
if($noerror) {
return (1, "");
} else {
return (0, "Es ist ein Fehler aufgetreten!");
}
}


HTH esskar
Echelon1010000
 2005-07-08 15:20
#56076 #56076
User since
2005-06-29
18 Artikel
BenutzerIn
[default_avatar]
Hi

Die eigentliche Fehlermeldung wird erst in der jeweiligen Sub ausgewertet die den Fehler verursacht. Das hat auch seinen Grund: Ich habe etwa 8 Subs die alle dafür zuständig sind bestehende Anzeigen auf dem LCD zu verändern. Eine davon habe ich oben gepostet: setstring(); Dazu weiter 8 die Anzeigen hinzufügen und entfernen. Beispielhaft hierfür ist die oben gezeigte sub addstring(); Schlussendlich noch einige weiter um Einstellungen clientseitig zu ändern etc.

Der Server antwortet jedoch auf eine ungültige Anweisung, um beim Beispiel zu bleiben, nicht mit genauen Informationen sondern nur mit einer Meldung wie "huh? unknown widget id". Dabei ist es ihm ziemlich egal ob das widget das ich bearbeiten wollte nun ein string war, ein Balken, eine Überschrift oder ein Scroller. Während ein nicht existentes (unknown) widget hier noch recht leicht zu identifizieren ist wird es bei der Fehlermeldung "incorrect parameter" schon kompliziert.

Dazu kommt noch das die Sub suck(); nicht weiss von welchem der 10 Module (CPU, Load, Net, News etc...) sie aufgerufen wurde. Sie kann also nicht spezifisch eine angepasste Fehlermeldung zurückgeben.

Um also die Fehlermeldung mit der aufrufenden Sub in Verbindung zu bringen muesste ich entweder die aufrufende Sub mit an die sub suck() übergeben oder die Fehlermeldung zurückgeben und die Auswertung in der aufrufenden sub setstring(); vornehmen.  Letzteres erschien mir programmtechnisch gesehen die bessere Lösung.

Im Erfolgsfall ist aber eigentlich garkeine Rückmeldung erfolgreich. Hier reicht IMO eine "1" völlig aus. Im Fehlerfall wird zurückgegeben was denn genau schief ging und die aufrufende Sub kann sich um die Auswertung kümmern. Um die Rückgabe stimmig zu gestalten kann ich jetzt die Serverantwort immer mit geben, dann fragt die aufrufende Sub eben nicht "if ( $answer != 1)" bzw. "if ( $answer ne "1") " sondern nach "if ( $answer ne "success")"
Das ist stilistisch gesehen sicher die sauberere Lösung, bei einem Programm an dem man noch entwickelt aber IMO noch nicht von tragender Bedeutung. Solcherlei läuft bei mir eher unter Optimierung und Cleaning und werden im gleichen Schritt bereinigt in dem auch Sachen wie (Pseudcode)
Code: (dl )
1
2
3
4
5
6
7
8
$starttime=time();
...
...
...
$enddtime=time();
$difftime=$endtime-$starttime;
$sleeptime=1-$difftime;
sleep ($sleeptime);

zu
Code: (dl )
1
2
3
4
5
$starttime=time();
...
...
$sleeptime=(1-(time()-$starttime));
sleep ($sleeptime);

vereinfacht werden.

Ich danke dir aber für deinen Hinweis :) Allerdings stellt sich mir noch eine Frage: Ich wäre jetzt davon ausgegangen immer den Rückgabewert des Servers zurückzugeben. Welchen Vorteil hätte die Methode zwei Rückgabewerte zurückzugeben, einmal eine Zahl und einmal die eigentliche Nachricht? Bekomme ich dadurch nicht eine doppelte Auswertung? Ich muss einmal in der sub suck(); prüfen welchen Wert ich zurückgeben muss und dann in der aufrufenden Sub welchen Wert ich empfangen hab, und anschliessend noch den Text auswerten. Bei der von mir angestrebten Methode gebe ich nur den Text zurück (sofern er keine Infonachricht ist) und lasse die Auswertung in der aufrufenden Sub laufen. Das "spart" meiner Ansicht nach ein paar Rechenzyklen. Einige meiner Messmodule sind recht zeitkritisch*, da kommt es schon ein wenig drauf an. Vielleicht hab ich da ja auch einen Denkfehler, dann korrigiere mich bitte ;)

Gruss, Echelon

*Ich habe einige Module bei denen es wichtig ist Daten in möglichst exakten Zeitabständen zu messen. Unter anderem das Modul bei dem ich den Durchsatz von Netzwerkinterfaces ermittele: Ich kann diesen nicht direkt abfragen sondern nur wieviele Bytes seit der letzten Abfrage durchgelaufen sind. Wenn ich den exakten Zeitraum kenne der seitdem verstrichen ist kann ich daraus aber den Durchsatz errechnen. Allerdings brauche ich da möglichst exakte Zeitabstände. Mit meinem Code funktioniert es auch, die Werte sind fast identisch mit denen die ich mit dem Tools "bmon" und "iftop" messe. Das meinte ich mit Zeitkritisch. Die Tatsache das die Kommunikation mit dem LCD Server recht langsam ist (Ein Schreibvorgang kann schon mal 0.5-0.7 Sekunden dauern)kommt hier erschwerend hinzu.
esskar
 2005-07-08 15:32
#56077 #56077
User since
2003-08-04
7321 Artikel
ModeratorIn

user image
hmmm...
aber eine string zu vergleichen dauert eben auch zeit.
außerdem kann man sich auf strings nicht verlassen, wenn sie nicht selber definiert sind...
was passiert, wenn sich das Protokoll ändert? dann musst du im ganzen code nach success suchen und es ggf. ändern.
wenn du die nachrichtvom server in der suck prozedur schon auswertest, brauchst du nur später dort das success ändern
vielleicht brauchst du auch die fehlermeldung mal nicht...
dann kannst du eben z.b.
Code: (dl )
suck(...) or die;

sagen oder andernfalls
Code: (dl )
1
2
my @result = suck(...);
$result[0] or die $result[1];

aber im grunde ist dir ja überlassen
Echelon1010000
 2005-07-08 16:22
#56078 #56078
User since
2005-06-29
18 Artikel
BenutzerIn
[default_avatar]
Hi

Da hast du allerdings recht, das hab ich so nicht bedacht. Die Fehlermeldung brauche ich aber schon.

Das heisst die eigentliche Auswertung muss in der Sub bleiben.
Wie gesagt, suck(); kennt nicht die aufrufende Sub. Erst recht nicht das Modul aus dem der Aufruf kam.

Es bekommt einen (von 1+Anzahl der Module) Socket übermittelt und verarbeitet die Antwort die darauf liegt.

Um also festlegen zu können welche Sub den Fehler verursacht hat und von welchem Modul diese aufgerufen wurde muss ich die Fehlermeldung der aufrufenden Sub zur Verfügung stellen.
Allerdings nur im Fehlerfall.
Falls kein Fehler auftritt muss die Sub keine Fehlermeldung bekommen, das ist ja im wesentlich auch das wie ich das geschrieben hab. Die Prüfung auf Erfolg/Misserfolg findet ja bereits in suck(); statt, es geht in der aufrufenden Sub nur um die Auswertung des Fehlers damit ich dann eine Ausgabe erzeugen kann wie:
Code: (dl )
1
2
3
print "Modul $modulname hat versucht ein nicht existierendes $typ widget mit dem Namen $widgetname zu verändern\n";
Oder halt :
print "Modul $modulname hat versucht das widget $widgetname mit dem Typ $typ zu erzeugen. Dieses widget existiert bereits"


Erfolg/Misserfolg wird ja in suck(); geprüft und entsprechend zurückgegeben.

Um das sauber zu gestalten werde ich den Code so anpassen das er immer einen String zurückgibt. Entspricht das dem was du meintest?

Gruss, Echelon
esskar
 2005-07-08 16:39
#56079 #56079
User since
2003-08-04
7321 Artikel
ModeratorIn

user image
wenn weißt du doch aber nicht, ob der string eine fehlermeldung enthält oder nicht! oder doch?

wie wärs mit einem modul ala
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package Sucker;

sub new {
...
}

sub suck(...) {
my $self = shift;

#
# auswertungen
#

unless(error) return 1;
else {
$self->{lasterror} = $message;
return 0;
}
}

sub getlasterror {
return shift->{lasterror};
}

1;


um rauszubekommen, welche funktion/package die suck funktion aufgerufen hat, kannst du caller benutzen

perldoc -f caller
Echelon1010000
 2005-07-08 16:58
#56080 #56080
User since
2005-06-29
18 Artikel
BenutzerIn
[default_avatar]
Hi
Im Moment ist es so das der Rückgabewert eine Fehlermeldung enthält wenn er <> 1 ist. In gewissem Sinne weiss ich das also schon.
Basierend auf deinem Vorschlag könnte ich mir sowas Vorstellen:

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
sub addstring
{
...
...
...
my $answer=suck($socket);
if ( $answer == 1 ) { return 1;}
if ( $answer == 2 ) {print "Modul $modul hat versucht auf widget $widgetname zuzugreifen, dieses Widget existiert nicht";return 0; }
if ( $answer == 3 ) { print "Es wurde durch Modul $modulname ein falscher Parameter für widget $widgetname übergeben";return 0; }
...
...
...
}

sub suck
{
...
...
...
  if ( index($message,"listen") >=0 ) { debug ("Discarded listen message ($message)");goto SUCKLOOP;}
  if ( index($message,"ignore") >=0 ) { debug ("Discarded ignore message ($message)");goto SUCKLOOP;}
  if ( index($message,"connect") >=0 ) { debug ("successfully registered as a client ($message)");return 1;}
  if ( index($message,"huh?") >=0 )
    {
     debug("error ($message)");
     if ( $message = "huh? unknown widget id" ){return 2;}
     if ( $message = "huh? incorrect parameter" ){return 3;}
     ....
     ....
     }
  if ( index($message,"success") >=0 ) { debug("success");return 1;}

}


Caller kenne ich schon. Ich verwende es in der Funktion getsocket(); um jedem aufrufenden Modul den passenden Socket zuzuordnen. Allerdings ist, laut Doku, die Funkion nur bedingt aussagefähig. Soweit ich weiss soll man sich nicht allzusehr darauf verlassen und mehr als 2 oder 3 Sprünge zurückgehen. (In meinem Fall muessen zw. 5 und 6 Sprünge zurückverfolgt werden: Modul -> Sub im Modul in der der Childprozess läuft -> Aufruf der Sub die mit dem Display kommuniziert -> Aufruf der Sub suck(); )
Der Ausschnitt asu der Doku den ich meine ist dieser:
Quote
              Be aware that the optimizer might have optimized call frames
              away before "caller" had a chance to get the information.  That
              means that caller(N) might not return information about the
              call frame you expect it do, for "N > 1".  In particular,
              @DB::args might have information from the previous time
              "caller" was called.


Daher schrecke ich da ein wenig vor zurück. Abgesehen davon das dieses Gerüst zusammen fällt wenn sich die Anzahl der Subs im Modul verändert. Wenn ich dort eine Sub einbaue die den Screen erzeugt "setupscreens();" und auch mit dem Display kommunizieren will stimmt die Anzahl der Funktionsaufrufe nicht mehr die vor der Sub liegen die den Subnamen ermitteln will.

Gruss, Echelon
<< >> 10 Einträge, 1 Seite



View all threads created 2005-07-08 12:34.