Hilfe bei TCP Server/Client

F

fontsix

Grünschnabel
Hallo zusammen.

Für das Studium sollen wir ein "kleines" Perl-Script zur Socketprogrammierung schreiben. Dabei soll ein Server und ein Client realisiert werden über TCP.

Das ist ansich kein Problem. Jedoch nachfolgende Aufgabe bereitet mir großen Kopfzerbrechen.

Der Client sollte auch sehr lange Zeichen(ketten) fehlerfrei an den Server übertragen können. Die Rede ist von 500 Zeilen z.B. eines Textes. Mit <STDIN> stösst man da schon an seine Grenzen. Nun wurde mehrfach in diesem Zusammenhang die Benutzung von Threads erwähnt. Da wir allerdings zu Perl absolut keine Vorlesung hatten, hoffe ich das hier jemand Rat hat.

Auch folgende Möglichkeiten könnte man in Betracht ziehen, fork(), select könnte ganz nützlich sein.

Ich habe schon versucht mich mit Threads im Internet ausführlich auseinanderzusetzen, allerdings sind die englischsprachigen Texte als Perl-Anfänger nicht gerade verständlich. Ich hoffe hier finden sich Leute die sich mit dieser Materie schon auseinandergesetzt haben und mir diesbezüglich irgendwie weiterhelfen könnten.

Über zahlreiche Antworten würde ich mich sehr freuen ...
 
Hi,

erstmal willkommen im Board!

Aber so ganz verstehe ich noch nicht was du genau wissen willst, ich habe nicht das Gefuehl dass dein Post konkrete Fragen beinhaltet. Von wegen Probleme mit dem einlesen via stdin, dann liess doch einfach aus einer Datei.


EDIT:
Ich hab mal geloescht was ich hier erst geschrieben hatte, denn ich hab noch ein viel besseres Beispiel gefunden:
http://www.perlfect.com/articles/sockets.shtml

Viel einfacher geht's kaum noch, und Englisch muss man auch nicht koennen, der Source Code ist so einfach dass man das auch so verstehen sollte...
Das Grundgeruest kannst du dann nach belieben erweitern.

Wobei ich dir schon raten wuerde so schnell wie moeglich an deinem Englisch zu arbeiten wenn dir das Probleme bereitet, das macht dein Leben als Programmierer deutlich einfacher.

MfG,
bytepool
 
Zuletzt bearbeitet:
Diese Standardbeispiele helfen mir nicht sonderlich weiter, da ich absoluter Perl-Anfänger bin.

Deswegen werde ich jetzt auch einfach mal meinen Code posten in der Hoffnung es kommen konkretere Antworten.


Server

Code:
#!/usr/bin/perl
#tcpserver.pl

print "TCP-Server\n\n\n";

use IO::Socket;
use IO::Select;
use strict;
use warnings;

# flush after every write
$| = 1;

my($lsn) = IO::Socket::INET->new( Listen => 5,
				  LocalPort => 5000, 
				  Proto => 'tcp', 
				  Blocking => 0,
				  ) or die "Error in Socket Creation : $!\n";

my($sel) = new IO::Select($lsn);

print "SERVER Waiting for client connection on port 5000\n";

while (my(@ready) = $sel->can_read) {
 foreach my $fh (@ready) {
  if ($fh == $lsn) {
   # Jetzt hast du eine Verbindung zu einem Client, über die du solange
   #Daten austauschen kannst bis sie beendet wird.
   # Ich würde $new_client z.B. in einem Array oder Hash unterbringen, da
   #du darüber den Client ansprichst.
   my($new_client) = $lsn->accept;
   $sel->add($new_client);

   # Was auch immer du mit $new_client vorhast, kommt hier
  }
  else {
   # In diesem Abschnitt wird einer der Clients etwas gesendet haben.
   # $client ist dasselbe wie $fh, ich leg zu meiner besseren Übersicht
   #halt immer $client an
   my($client) = $fh;
   my($string) = <$client>; # Jetzt hast du in $string das stehen, was ein
			    #Client gesendet hat.

   # Was auch immer du mit nem String machen willst, das kommt dann hier.
   print "Daten: $string\n";
  }
 }
}

Client

Code:
#!/usr/bin/perl
#tcpclient.pl

print "TCP-Client\n\n\n";

use IO::Socket::INET;
use strict;
use warnings;

my ($data,$length);

# flush after every write
$| = 1;

# creating object interface of IO::Socket::INET modules which internally creates
# socket, binds and connects to the TCP server running on the specific port.
my $socket = new IO::Socket::INET (
				PeerHost => '127.0.0.1',
				PeerPort => '5000',
				Proto => 'tcp',
				Blocking => 0,
				) or die "ERROR in Socket Creation : $!\n";

print "TCP Connection Success.\n\n";


# read the socket data sent by server.
    
while(1)
  {
    print "Bitte Text eingeben : ";
    $data= <STDIN>;   
    $socket->send("$data\0");
    $socket->recv($data,1024);
    print ("Received from Server : $data\n\n");
    }
$socket->shutdown();

Das Problem ist bei obigem Code das der Server einfach keine Daten vom Client bekommt die ich per <STDIN> einlese. Wie gesagt sollte nun der Client die Daten an den Server senden und der Server soll die Daten gleich wieder zum Client zurückschicken, wenn möglich ohne Zeichenverlust, auch bei sehr langen Zeichenketten. Danach soll gleich wieder eine Abfrage von <STDIN> möglich sein.

Und nun nochmal zu dem Thema blockierende Sockets. Ich habe im Internet folgendes Beispiel gefunden

Code:
#!/usr/bin/perl
my $pid = fork();
 if (not defined $pid) {
 print “resources not avilable.\n”;
 } elsif ($pid == 0) {
 print “IM THE CHILD\n”;
 sleep 5;
 print “IM THE CHILD2\n”;
 exit(0);
 } else {
 print “IM THE PARENT\n”;
 waitpid($pid,0);
 }
 print “HIYA\n”;


Kann man das so in etwa für meinen Server übernehmen ? so das der 1. Childprozess die Daten liest und der 2. Childprozess die Daten schreibt ?
Ich hoffe es kann jemand helfen. So langsam bin ich am verzweifeln. :(
 
Hi,

ich wuerde erstmal den Server unabhaengig vom Client testen, damit du weisst dass der Server funktioniert bevor du mit dem Client beginnst. Mit netcat (nc) kannst du eine einfache TCP Verbindung aufbauen und Text an deinen Server senden ohne dass du deinen Client benutzen musst.

Dann wuerdest du vermutlich schnell feststellen dass dein Server schon fast funktioniert.

Den Beispiel Code den du dir ausgesucht hast waere nicht meine erste Wahl gewesen, extrem unangenehme Variablen Benennung und Formatierung, aber halbwegs lauffaehig.
Ich hab deinen Server Code mal ein bisschen aufgeraeumt damit das ganze lesbar wird:

Code:
#!/usr/bin/perl                                                                                                                                                                                

use strict;
use warnings;

use IO::Socket;
use IO::Select;

print "TCP-Server\n\n\n";
# flush after every write                                                                                                                                                                                      
$| = 1;

my $listen_socket = IO::Socket::INET->new( Listen => 1,
                                    LocalPort => 5000,
                                    Proto => 'tcp',
                                    Blocking => 0,
    ) or die "Error in Socket Creation : $!\n";

my $sel = new IO::Select($listen_socket);

print "SERVER Waiting for client connection on port 5000\n";

while (my @ready = $sel->can_read)
{
    foreach my $fd (@ready)
    {
        if ($fd == $listen_socket)
        {
            print "Accepting new client, adding to select group\n";
            my $new_client = $listen_socket->accept();
            $sel->add($new_client);
        }
        else
        {
            my $data = <$fd>;
            if ($data)
            {
                print "Received from client: $data";
            }
            else
            {
                $sel->remove($fd);
            }
        }
    }
}

Jetzt wird auch der Client fd wieder aus dem select set genommen wenn die Verbindung vom Server geschlossen wird. Allerdings wuerde ich eigentlich auch die Client Verbindungen in einem Array festhalten damit die Sockets auch auf Server Seite wieder sauber geschlossen werden.

Threads oder das forken von Prozessen ist nicht noetig, da du ja schon select benutzt. Um den Server ansprechbar zu halten auch wenn er groessere Stuecke Text verarbeiten soll, kannst du ihn stattdessen nur kleine Stuecke pro Durchlauf verarbeiten lassen, indem du die gelesen Daten ("my $data = <$fd>;") auf eine bestimmte Laenge festlegst. Keine Ahnung wie das in Perl geht, muesste ich auch nachlesen. Wenn ich mich recht erinnere, dann liest <> aber sowieso Zeilenweise, aber besser waere sicherlich byteweise.

Sorry falls meine Antworten ein wenig herablassend klingen sollten, ich hab in letzter Zeit nicht viel geschlafen. ;p

MfG,
bytepool
 
Vielen Dank für die schnelle Antwort. Macht doch nix, das mit dem wenig schlafen kenne ich. Der Server funktioniert also nun so weit, und um das ganze nun wieder zurückzusende muss ich solch ähnliches Konstrukt auch beim Client anwenden ?

Oder kann man das auch einfacher realisieren ? Denn wenn ich ehrlich bin blicke ich bei dem Code im Server schon lange nicht mehr durch :(
 
Hi,

der Server macht im Augenblick noch nichts ausser auf stdout auszugeben was er rein bekommt. Wenn du an den Client was zurueck schicken willst, musst du noch die Client Verbindungen speichern (Rueckgabe von accept), und dann was in die Richtung $client->send($data); einbauen, wenn du vom Client Socket lesen kannst.

An deinem Client Code musst du glaub ich nicht viel aendern, nur den Socket blocking machen.

Was ich nicht so ganz verstehe wie du ueberhaupt an die Aufgabe kommst, du scheinst damit ja ein bisschen ueberfordert zu sein, woraus ich schliesse dass dir Hintergrundwissen fehlt. Non-blocking Sockets mit select ist ein standard Konstrukt in der Netzwerkprogrammierung, aber das wuerde ich auch nicht direkt Programmieranfaengern auf's Auge druecken. Einfacher zu verstehen sind normalerweise erstmal blocking Sockets, aber das muesste dann in der Tat mit Threads gemacht werden, und wenn ich das richtig verstehe kennst du dich mit Threads auch nicht aus.
Es gibt jede Menge Tutorials und Buecher zu den Themen, ich denke da wirst du dich einfach einlesen muessen. Versuch einfach den Code Schritt fuer Schritt nach zu vollziehen und schlage Funktionen nach die du nicht kennst, in diesem Fall insbesondere select, socket, accept und send.

MfG,
bytepool
 
Vielen Dank nochmal für deine Hilfe. Ich werde mich daran nochmal ausprobieren.

Wir haben die Aufgabe von unserem Professor bekommen das in Perl zu realisieren ohne jemals eine Vorlesung zu dem Thema gehabt zu haben. So ist das Leben, leider. Und wer das nicht auf die Reihe bekommt, bekommt die Zulassung zur Prüfung nicht. Würde ich Informatik studieren könnte ich es ja noch verstehen, aber einen so ins kalter Wasser zu werfen, naja. Anmerkung der Rede: Ich studiere Elektro- und Informationstechnik. :)
 
So Aufgabe wurde jetzt noch unnötig dadurch verkompliziert das es auch möglich sein muss eine Textdatei einzulesen, wie gehabt 500 Zeilen, in denen aber auch Zeilenumbrüche enthalten sein können. Bei Texten die einfach nur alle Zeichen hintereinander enthalten mag die letzte Methode ja noch funktionieren. Ich weiß einfach nicht mehr weiter. Hat noch jemand Vorschläge in diesem Fall bietet sich vielleicht das zeichenweise Einlesen an, aber sicher bin ich mir da natürlich auch wieder nicht. Zudem hat im Labor jetzt nicht mal die Verbindung zwischen Server und Client geklappt als ich die Parameter mit ARGV übergeben habe :(

LG fontsix
 
Hej,

Naja, ich bin im Allgemeinen auch der Meinung dass man im Studium in der Lage sein sollte sich nach einer allgemeinen Programmiervorlesung neue Sprachen selbst anzueignen. Aber das ist natuerlich auch vom Zeitrahmen abhaengig, wenn euer Prof euch jetzt letzte Woche gesagt hat ihr sollt bitte in 2 Wochen ein fertiges Perl Programm haben, dann ist das recht hart.

Hast du keinen Studienkollegen oder so der das kann? Wenn du Pech hast wird das uebernehmen einer Loesung die dir jemand in einem Forum vorgelegt hat als Taeuschungsversuch angesehen. ;)

Aber ich hab heute meinen weichen Tag: ;)

Code:
#!/usr/bin/perl

use strict;
use warnings;

use IO::Socket;
use IO::Select;

print "Server: Starting echo server\n";

my $port = 5000;
my $host = "localhost";

my $listen_socket = IO::Socket::INET->new( Listen => 1,
				    LocalPort => $port, 
				    LocalHost => $host,
				    Proto => 'tcp', 
				    Blocking => 0,
    ) or die "Error during creation of listening socket: $!\n";

my $select = new IO::Select($listen_socket);

print "Server: Waiting for client connection on port $port\n";

while (my @ready = $select->can_read) 
{
    foreach my $connection (@ready)
    {
	if ($connection == $listen_socket) 
	{
	    my $client = $listen_socket->accept();
	    print "Server: Client $client accepted\n";
	    $select->add($client);
	}
	else
	{
	    my $data = <$connection>;

	    if ($data)
	    {
		print "Server: Received: $data";
		$connection->send("SERVER: " . $data);
	    }
	    else
	    { 
		print "Server: Closing connection to $connection\n";
		$select->remove($connection);
		close($connection);
	    }
	}
    }
}

Code:
#!/usr/bin/perl

use strict;
use warnings;

use IO::Socket::INET;

print "Starting client\n";

my $host = "localhost";
my $port = 5000;

my $file;# = "foo.pl";

my $socket = new IO::Socket::INET (
    PeerHost => $host,
    PeerPort => $port,
    Proto => 'tcp',
    ) or die "ERROR in Socket Creation : $!\n";

print "TCP connection successful\n";

if ($ARGV[0])
{
    $file = $ARGV[0];
}

if ($file)
{
    open FILE, $file or die $!;
    while(<FILE>)
    {
	my $data= $_;
	$socket->send($data);
	$data = <$socket>;	#->recv($data, 1024);
	print "Received from Server: " . $data;
    }
}
else
{
    print "Input: ";

    while(<STDIN>)
    {
	my $data= $_;
	$socket->send($data);
	$data = <$socket>;	#->recv($data, 1024);
	print "Received from Server: " . $data;
	print "Input: "
    }
}

close($socket);

Der Server arbeitet jetzt Zeilenweise, d.h. wenn du extrem lange Zeilen dabei haettest, und mit mehreren Clients gleichzeitig arbeiten wuerdest, bekaemst du deutliche Verzoegerungen bei der Verarbeitung.

MfG,
bytepool
 
Hey bytepool, ich habe dich nicht vergessen, ich hatte nur die letzen Tage sehr viel um die Ohren. Ich hab das Programm heute abnehmen lassen und es hat einwandfrei funktioniert. Vielen vielen Dank nochmal für deine Hilfe.

MfG
fontsix
 
Hej,

Naja, ich bin im Allgemeinen auch der Meinung dass man im Studium in der Lage sein sollte sich nach einer allgemeinen Programmiervorlesung neue Sprachen selbst anzueignen. Aber das ist natuerlich auch vom Zeitrahmen abhaengig, wenn euer Prof euch jetzt letzte Woche gesagt hat ihr sollt bitte in 2 Wochen ein fertiges Perl Programm haben, dann ist das recht hart.

Hast du keinen Studienkollegen oder so der das kann? Wenn du Pech hast wird das uebernehmen einer Loesung die dir jemand in einem Forum vorgelegt hat als Taeuschungsversuch angesehen. ;)

Aber ich hab heute meinen weichen Tag: ;)

Code:
#!/usr/bin/perl

use strict;
use warnings;

use IO::Socket;
use IO::Select;

print "Server: Starting echo server\n";

my $port = 5000;
my $host = "localhost";

my $listen_socket = IO::Socket::INET->new( Listen => 1,
				    LocalPort => $port, 
				    LocalHost => $host,
				    Proto => 'tcp', 
				    Blocking => 0,
    ) or die "Error during creation of listening socket: $!\n";

my $select = new IO::Select($listen_socket);

print "Server: Waiting for client connection on port $port\n";

while (my @ready = $select->can_read) 
{
    foreach my $connection (@ready)
    {
	if ($connection == $listen_socket) 
	{
	    my $client = $listen_socket->accept();
	    print "Server: Client $client accepted\n";
	    $select->add($client);
	}
	else
	{
	    my $data = <$connection>;

	    if ($data)
	    {
		print "Server: Received: $data";
		$connection->send("SERVER: " . $data);
	    }
	    else
	    { 
		print "Server: Closing connection to $connection\n";
		$select->remove($connection);
		close($connection);
	    }
	}
    }
}

hi

sorry wenn ich mich nach ein paar monaten hier dran hänge....
herzlichen dank für den code, hat mir sehr geholfen und mich ein gutes stück weitergebracht!

jetzt brüte ich darüber wie es möglich ist, hier ein timeout reinzubringen. wenn ich daten an das skript sende und mir stirbt die dsl verbindung und ich baue eine neue conneciton auf, dann sehe ich auf dem rechner wo das skript läuft mit netstat noch eine aktive verbindung zu der alten IP.
hättest du da eine idee wie ich das problem lösen kann?

nochmals danke!

grüße
 
Hi,

einfacher ist es wenn du statt vom Skript vom Client oder Server sprichst. Ich hab einige Male lesen muessen um zu verstehen was du meinst. ;)

Ich muss gestehen dass ich dachte dass es einen standard TCP timeout gaebe, fuer den Fall dass laengere Zeit nichts mehr ueber eine Verbindung geschickt wird. Ich erinnere mich dass wir in Python mal das Problem hatten dass wir die Verbindung aufrecht erhalten wollten, sie aber nach 5 Minuten ohne Datenuebertragung automatisch geschlossen wurde...

Ich hab grad mal getestet eine Verbindung mit nc zum Server aufzubauen und nc dann mit ctrl-z schlafen zu schicken, um zu testen ob es einen timeout gibt, aber auch nach 15 Minuten ist die Verbindung noch "ESTABLISHED". Allerdings sehe ich auch grade dass das natuerlich ein bloedsinniger Test ist, nc selber muss keepalive messages und anderen TCP Kram ja gar nicht beantworten, das macht der Netzwerkstack vom OS...

Was du auf Applikationsebene machen kannst, ist ein Hash/Dictionary (wie immer die Datenstruktur in Perl heisst) zu erstellen in dem du fuer jede Verbindung den Zeitpunkt der letzten Aktivitaet speicherst. Dann gibst du select einen timeout mit, und gehst bei jedem select timeout die Liste mit den letzten Aktivitaeten durch, und guckst ob die Verbindung wegen zu langer Inaktivitaet geschlossen werden soll.

Alternativ kannst du die Socket Option SO_KEEPALIVE fuer die Client Verbindungen aktivieren und die keepalive time im Kernel entsprechend setzen (z.B. "sysctl net.ipv4.tcp_keepalive_time=300") [1]. Wahrscheinlich kannst du das Verhalten auch durch Socket Optionen alleine erreichen [2], musst du mal gucken, da ist einiges moeglich (perldoc IO::Socket, man tcp).

Letzteres sollte am einfachsten und saubersten sein.

[1] http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
[2] http://cpansearch.perl.org/src/SALVA/Socket-Linux-0.01/samples/keepalive.pl

MfG,
bytepool
 
Zuletzt bearbeitet:

Ähnliche Themen

Server und Client für TCP und UDP

Hilfe bei backup-script

Problem mit Apache2 + MySQL Server

Exchange Ersatz und anmeldung der Clients über VPN

mysql-server lässt sich nicht starten...

Zurück
Oben