Christian Seiler: Multithreaded Server - $SIG{CHLD} = sub { wait } beendet Server

Beitrag lesen

Hallo Christian,

Lasse ich den SIGCHLD-Handler ganz weg, bekomme ich natürlich Zombies. Was aber, wenn ich die PID des Kind verwenden möchte? Mit

$SIG{'CHLD'} = sub { my $c = wait(); print "Child $c terminated.\n" };


> wird der Serverprozess beendet, sobald das erste Kind stirbt, was natürlich sinnlos ist. Die Meldung "Child ... terminated" erscheint noch, danach wird die while-Schleife des Servers verlassen und dieser terminiert.  
  
Erstens, aus der Perl-Doku (perldoc perlipc):  
  
|       On most Unix platforms, the "CHLD" (sometimes also known as "CLD") signal has special behavior with respect to a value of ’IGNORE’.  
|       Setting $SIG{CHLD} to ’IGNORE’ on such a platform has the effect of not creating zombie processes when the parent process fails to  
|       "wait()" on its child processes (i.e. child processes are automatically reaped).  Calling "wait()" with $SIG{CHLD} set to ’IGNORE’ usu‐  
|       ally returns "-1" on such platforms.  
  
Also nix Zombies bei 'IGNORE'. (Wobei Du dann aber auch keine Meldungen ausgeben lassen kannst, wenn der Prozess weg ist).  
  
Woran Dein Problem jedoch scheitert, ist Deine accept()-Schleife. Durch ein Signal (das nicht ignoriert wird) werden nämlich System Calls unterbrochen. Dein accept() wird also unterbrochen, kehrt zurück, liefert aber keinen Socket, weswegen Du die Schleife fälschlicherweise beendest.  
  
perldoc perlipc macht das ganze über eine globale Variable, ich passe das mal kurz an:  
  
~~~perl
my $waitedpid = 0;  
  
sub childhandler {  
  my $child;  
  # Auf ALLE kinder warten  
  while (($waitedpid = waitpid(-1,WNOHANG)) > 0);  
  # Portabilität mit Systemen, auf denen der Signalhandler neu gesetzt  
  # werden muss, nachdem er ausgeführt wurde  
  $SIG{'CHILD'} = \&childhandler;  
}  
  
$SIG{'CHILD'} = \&childhandler;

und dann in der Schleife:

for (  
  $waitedpid = 0;  
  ($client = $sock->accetp ()) || $waitedpid;  
  $waitedpid = 0, $client->close) {  
    next if $waitedpid and not $sock;  
    # Hier jetzt der normale Code vom inneren der Schleife ohne das  
    # $client->close weil das schon in der for()-Bedingung gemacht wird  
}

Außerdem verstehe ich nicht, wieso ich z.B. im Serverprozess die Client-Verbindung beenden kann (s. (**)), diese aber im Kindprozess dann noch zur Verfügung steht. Die Variablen werden ja kopiert, aber der Socket ist doch trotzdem nur einmal da. Das sieht irgendwie aus, als würden beide Prozesse am selben Objekt operieren, ohne zu wissen, was der andere tut. Das kann doch nicht gut gehen.

Du missverstehst etwas: $socket->close schließt die Verbindung nicht. $socket->close schließt nur den Socket. Wenn der letzte Socket, der mit der Verbindung assoziiert war, geschlossen wird, erst DANN wird die eigentliche Verbindung geschlossen. Wenn Du nun fork() aufrufst, werden alle Dateideskriptoren (inklusive Sockets) dupliziert - d.h. im neuen Prozess hast Du ein anderes Socket, aber die gleiche Verbindung "hinter" dem Socket. Wenn Du also im Elternprozess das Socket schließt, dann schließt Du nicht die Verbindung, da der Kindprozess noch ein Socket auf die Verbindung besitzt.

Viele Grüße,
Christian