fork/non blocking HTTP::Daemon

fork/non blocking HTTP::Daemon

am 29.05.2006 22:40:13 von robert.henniger

Hallo Newsgroup


Ich probiere im Moment einen eigenen kleinen HTTP-Daemon zu
prgrammieren.
Als Grundlage habe ich mir die Klasse HTTP::Daemon ausgesucht. Er soll
später unter Linux und Windows laufen. Im Moment wird er unter Windows
programmiert. Er soll später unter fest definierten URLs bestimmt
Aktionen ausführen (Mail, Image, FTP, Datnsicherung, etc).

Alle einzelnen Anfragen funktionieren einwandfrei. Da dieser Server
jedoch auch länger an manchen Requests zu rechnen haben wird, ist es
notwendig das er den Socket(!?) nicht blockiert. Er soll in der
Rechenzeit weiterhin ansprechbar sein.

Ist aber gestartet. Ich muss zugeben das mit fork auch noch nicht ganz
verstanden zu haben.
fork erzeugt doch ein 100% gleiches Abbild vom Perl-Interpreter. Aber
wie läuft das dann mit den offenen Sockets und so weiter.
Vielleicht kann mir da jemand noch ein paar Hintergrundinformationen
geben.

Ich habe hier ein lauffähiges Testskript welches als
Single-Request-Server einwandfrei funktioniert.
Wenn ich jedoch die fork-Anweisunge auskommentiere, nimmt er keine
Requests mehr an. Er reagiert gar nicht mehr.
Ich habe gelesen das die threads unter perl und windows noch nicht
ausgereift sind. Ist die fork Lösung gut für diesen Einsatz? Gibt es
bessere Lösungen?
Über code-Beispiele wäre ich sehr dankbar.

Aber/Und...


Ich bin für jede Hilfe dankbar.



Gruß Robert


Anbei die Listings.

############################################################ ###############=
####
service.pl
############################################################ ###############=
####
use strict;
use warnings;

use FindBin;
use CGI;
use CGI::Log;
use HTTP::Daemon;
use HTTP::Status;
use HTTP::Response;


use lib $FindBin::Bin.'/objects';

use Server;



my $init =3D {
port =3D> "90",
localaddr =3D> "127.0.0.1",
serverprotocol =3D> "tcp",
htdocs =3D> $FindBin::Bin."/htdocs/",
logdir =3D> $FindBin::Bin."/logs/",
};



my $service =3D Server->new($init);
if($service){
### Bind the standart callbacks
$service->bind();

$service->start();
}


print "finish process\n";





############################################################ ###############=
####
Server.pm
############################################################ ###############=
####
package Server;
use strict;
use warnings;


sub new {

my $class =3D shift;
my $init =3D shift;
my $self =3D {};

bless($self,$class);



$self->{"RUNNING_INSTANCE"} =3D undef;
$self->configure($init);



my @keys =3D keys(%{$self->{"PUBLIC_CONFIG"}});

if($self->{"PUBLIC_CONFIG"}->{"LocalPort"}){
my $serverinstanz =3D HTTP::Daemon->new(
LocalPort =3D> 90, LocalAddr =3D> "127.0.0.1", Proto =3D> "tcp"
);

if($serverinstanz){
$self->{"RUNNING_INSTANCE"} =3D $serverinstanz;
return $self;
}

}
}



sub bind {
my $self =3D shift;
print "testbind\n";
}


sub start {
my $self =3D shift;


$SIG{PIPE} =3D 'IGNORE';
# Reaper für terminierte Kindprozesse
#$SIG{CHLD} =3D sub { wait(); };


print $self->{"RUNNING_INSTANCE"}->url."\n";

if($self->{"RUNNING_INSTANCE"}){

while(my $client =3D $self->{"RUNNING_INSTANCE"}->accept){

# next if $pid;
# fork();

# # Kind bearbeitet Requests der Verbindung
while(my $request =3D $client->get_request){
### Process Request

#sleep(50);



my $response =3D HTTP::Response->new();
$response->code(200);
$response->message("OK");
$response->header("Content-Type" =3D> "text/html");
$response->header("Pragma" =3D> "no-cache");
$response->content("test\n
".$request->url->path."");
$client->send_response($response);




$client->close();
print "connection closed\n";
}
}
}else{
print "server not started\n";
}

}


sub shutdown {

}



sub restart {

}



sub configure {

my $self =3D shift;
my $init =3D shift;


my $public =3D {
localaddr =3D> "LocalAddr",
port =3D> "LocalPort",
serverprotocol =3D> "Proto",
};


my $privat =3D {};


if(ref($init) eq "HASH"){

my @configParam =3D keys(%{$init});
for(my $c=3D0;$c
if($public->{$configParam[$c]}){
$self->{"PUBLIC_CONFIG"}->{ $public->{$configParam[$c]}} =3D
$init->{$configParam[$c]};
}else{
$self->{"PRIVAT_CONFIG"}->{$configParam[$c]} =3D
$init->{$configParam[$c]};
}
}
return 1;
}else{
return undef;
}

}




1;

Re: fork/non blocking HTTP::Daemon

am 30.05.2006 07:43:16 von Christian Winter

design4future schrieb:
> Ich probiere im Moment einen eigenen kleinen HTTP-Daemon zu
> prgrammieren.
> Als Grundlage habe ich mir die Klasse HTTP::Daemon ausgesucht. Er soll
> später unter Linux und Windows laufen. Im Moment wird er unter Windows
> programmiert. Er soll später unter fest definierten URLs bestimmt
> Aktionen ausführen (Mail, Image, FTP, Datnsicherung, etc).
>
> Alle einzelnen Anfragen funktionieren einwandfrei. Da dieser Server
> jedoch auch länger an manchen Requests zu rechnen haben wird, ist es
> notwendig das er den Socket(!?) nicht blockiert. Er soll in der
> Rechenzeit weiterhin ansprechbar sein.
>
> Ist aber gestartet. Ich muss zugeben das mit fork auch noch nicht ganz
> verstanden zu haben.
> fork erzeugt doch ein 100% gleiches Abbild vom Perl-Interpreter. Aber
> wie läuft das dann mit den offenen Sockets und so weiter.
> Vielleicht kann mir da jemand noch ein paar Hintergrundinformationen
> geben.

"perldoc perlipc" hat ein paar ganz interessante Infos zum Umgang
mit fork(). Ansonsten könnte auch ein Blick in die Sources von
NetServer::Generic hilfreich sein, dort sind die wichtigen Stellen
(Forking, Parent-Children-Kommunikation etc.) ganz gut dokumentiert.

Viele Grüße
-Christian

Re: fork/non blocking HTTP::Daemon

am 30.05.2006 07:44:54 von Manni Heumann

design4future wrote:

> fork();
> # # Kind bearbeitet Requests der Verbindung
> while(my $request = $client->get_request){

Leider stellst Du nicht sicher, daß auch wirklich das passiert, was Du da in
Deinem Kommentar angegeben hast. Sowohl Kind wie Elternteil werden versuchen,
die Requests der Verbindung zu bearbeiten. fork() liefert die neue PID an das
Elternteil zurück. Das Kind bekommt von fork() den Wert 0.
Wenn der Rückgabewert von fork() also wahr ist, kümmerst Du Dich nicht um den
Request, nur wenn er 0 ist.

Außerdem solltest Du ein "exit 0" an der Stelle einbauen, an der das Kind mit
seiner Arbeit fertig ist, sonst wird auch das Kind versuchen, die while-
Schleife noch einmal zu durchlaufen, um dann selber wieder ein Kind zu
forken.

Wie Du selber schon geschrieben hast, erzeugt fork() eine Kopie. Perl weiß
nicht von sich aus, welche Aufgabe Du dem Kind zugedacht hast.


Manni

Re: fork/non blocking HTTP::Daemon

am 30.05.2006 12:36:14 von robert.henniger

Hallo Christian, Hallo Manni.

Vielen Dank für die Antworten ich werde die Vorschläge mir heute
gleich anschauen und würde dann gerne gegebenenfalls auf eure Hilfe
zurück kommen. Ich hoffe das ist OK.


Gruß robert

Re: fork/non blocking HTTP::Daemon

am 02.06.2006 16:47:21 von robert.henniger

Manni Heumann schrieb:

> design4future wrote:
>
> > fork();
> > # # Kind bearbeitet Requests der Verbindung
> > while(my $request =3D $client->get_request){
>
> Leider stellst Du nicht sicher, daß auch wirklich das passiert, was Du =
da in
> Deinem Kommentar angegeben hast. Sowohl Kind wie Elternteil werden versuc=
hen,
> die Requests der Verbindung zu bearbeiten. fork() liefert die neue PID an=
das
> Elternteil zurück. Das Kind bekommt von fork() den Wert 0.
> Wenn der Rückgabewert von fork() also wahr ist, kümmerst Du Dich nich=
t um den
> Request, nur wenn er 0 ist.
>
> Außerdem solltest Du ein "exit 0" an der Stelle einbauen, an der das Ki=
nd mit
> seiner Arbeit fertig ist, sonst wird auch das Kind versuchen, die while-
> Schleife noch einmal zu durchlaufen, um dann selber wieder ein Kind zu
> forken.
>
> Wie Du selber schon geschrieben hast, erzeugt fork() eine Kopie. Perl wei=
ß
> nicht von sich aus, welche Aufgabe Du dem Kind zugedacht hast.
>
>
> Manni









Hallo Manni.

Ich habe probiert deine Anweisungen umzusetzen, leider jedoch ohne
Erfolg.
Aus diesem Grund poste ich wieder den angeänderten Code. Dieser nimmt
zwar Requests an und verarbeitet sie jedoch blockt er immernoch.

Vielleicht kannst du mir helfen diese Zeilen zum Laufen zu bekommen.

perldoc perlipc habe ich mir durchgelesen, NetServer::Generic
angeschaut. Hat mich nicht weiter gebracht und ich konnte keien
verwertbaren Informationen für mich herausziehen.

Jedoch eines ist mir aufgefallen. Bei Netserver::Generic wird nahch dem
forken ein neuer Socket erstellt.
Ist dies erforderlich für einen non-blocking Server?

Vielleicht kann mir jemand sehr leicht erklären wie das intern
abläuft.

Ich stelle es mir so vor:
Der Server wartet auf Requests. Wenn ere einen bekommen hat, splittet
er den Prozess auf und lässt den Child-Prozess(?) den Request/ Socket
bearbeiten. Nach gemachter Arbeit zerstört sich der Prozess mit exit
0 Der Hauptprozess wartet in dieser Zeit schon lange wieder auf neue
Requests.
Meine Frage, "woher weiss der Server welcher Client welchen output
bekomm"?.
Wenn ich an diesen Server 3...6 Anfragen stelle, die von der gleichen
IP stammen, ist doch für den Server nicht merh nachvollziehbar welcher
Socket zu welchem CLient gehört. Oder?

Hier wäre mir eine Aufklärung sehr hilfreich.


Jetzt der geänderte Code


############################################################ ###############=
####

####Server.pm
############################################################ ###############=
####



package Server;
use strict;
use warnings;


sub new {

my $class =3D shift;
my $init =3D shift;
my $self =3D {};

bless($self,$class);



$self->{"RUNNING_INSTANCE"} =3D undef;
$self->configure($init);



my @keys =3D keys(%{$self->{"PUBLIC_CONFIG"}});

if($self->{"PUBLIC_CONFIG"}->{"LocalPort"}){
my $serverinstanz =3D HTTP::Daemon->new(
LocalPort =3D> 90, LocalAddr =3D> "127.0.0.1", Proto =3D> "tcp"
);

if($serverinstanz){
$self->{"RUNNING_INSTANCE"} =3D $serverinstanz;
return $self;
}

}
}



sub bind {
my $self =3D shift;
print "testbind\n";
}


sub start {
my $self =3D shift;
print "pid of startded server: ".$$."\n";

$SIG{PIPE} =3D 'IGNORE';
# Reaper für terminierte Kindprozesse
#$SIG{CHLD} =3D sub { wait(); };
$SIG{CHLD} =3D 'IGNORE';


print $self->{"RUNNING_INSTANCE"}->url."\n";

if($self->{"RUNNING_INSTANCE"}){

while(my $client =3D $self->{"RUNNING_INSTANCE"}->accept){

# next if $pid;
# fork();

# # Kind bearbeitet Requests der Verbindung
while(my $request =3D $client->get_request){
### Process Request

#sleep(50);
my $pid =3D fork();
print "pid after forking: ".$pid."\n";
if($pid == 0){



print "now the child ($$) is sleeping for 5 seconds\n";
sleep(5);
print "wake up ($$)\n";



my $response =3D HTTP::Response->new();
$response->code(200);
$response->message("OK");
$response->header("Content-Type" =3D> "text/html");
$response->header("Pragma" =3D> "no-cache");
$response->content("test\n
".$request->url->path."");
$client->send_response($response);




$client->close();
print "connection closed\n";
exit 0;
}else{
print "code of the parent pid\n";
#exit 0;
}
}
}
}else{
print "server not started\n";
}

}


sub shutdown {

}



sub restart {

}



sub configure {

my $self =3D shift;
my $init =3D shift;


my $public =3D {
localaddr =3D> "LocalAddr",
port =3D> "LocalPort",
serverprotocol =3D> "Proto",
};


my $privat =3D {};


if(ref($init) eq "HASH"){

my @configParam =3D keys(%{$init});
for(my $c=3D0;$c
if($public->{$configParam[$c]}){
$self->{"PUBLIC_CONFIG"}->{ $public->{$configParam[$c]}} =3D
$init->{$configParam[$c]};
}else{
$self->{"PRIVAT_CONFIG"}->{$configParam[$c]} =3D
$init->{$configParam[$c]};
}
}
return 1;
}else{
return undef;
}

}




1;



############################################################ ###############=
####



Vielen Dank im Vorraus.

Gruß Robert

Re: fork/non blocking HTTP::Daemon

am 02.06.2006 19:54:02 von Manni Heumann

design4future wrote:

> Jedoch eines ist mir aufgefallen. Bei Netserver::Generic wird nahch dem
> forken ein neuer Socket erstellt.
> Ist dies erforderlich für einen non-blocking Server?

Nein.

> Vielleicht kann mir jemand sehr leicht erklären wie das intern
> abläuft.
>
> Ich stelle es mir so vor:
> Der Server wartet auf Requests. Wenn ere einen bekommen hat, splittet
> er den Prozess auf und lässt den Child-Prozess(?) den Request/ Socket
> bearbeiten. Nach gemachter Arbeit zerstört sich der Prozess mit exit
> 0. Der Hauptprozess wartet in dieser Zeit schon lange wieder auf neue
> Requests.

Genau.

> Meine Frage, "woher weiss der Server welcher Client welchen output
> bekomm"?.
> Wenn ich an diesen Server 3...6 Anfragen stelle, die von der gleichen
> IP stammen, ist doch für den Server nicht merh nachvollziehbar welcher
> Socket zu welchem CLient gehört. Oder?

Ich lasse sowas vom Betriebssystem machen und kümmere mich nicht weiter
drum. Aber vielleicht hilft Dir ja schon mein Vorschlag für Deinen Code.
....

> while(my $client = $self->{"RUNNING_INSTANCE"}->accept){
>

Ich würde hier forken. Ein Kind pro Client macht einfach mehr Sinn als ein
Kind pro Client-Request. Schon weil wir ja nicht wissen, wie der arme
Client die Antworten bearbeitet.

> # next if $pid;
> # fork();
>
> # # Kind bearbeitet Requests der Verbindung
> while(my $request = $client->get_request){
> ### Process Request
>
> #sleep(50);
> my $pid = fork();

Wie gesagt, das fork() kommt an der Stelle IMHO zu spät.


HTH,
Manni