Hilfe bei TCP Server/Client
Gast
- perl
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,
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.
In der Welt der Handler gibt es keine Zeichenketten, sondern nur noch Bytes. Was die Bytes darstellen ist völlig Wurscht ;)
Auch folgende Möglichkeiten könnte man in Betracht ziehen, fork(), select könnte ganz nützlich sein.
Wenn der Server mehrere Connections handeln soll, ist fork() angesagt. Teste mal ohne fork() nur eine Connection, Code untenstehend und teste die 'Grenzen' aus:
############### server
#!/usr/bin/perl
###########################################################################
# Server, der an einem bestimmten Port lauscht
###########################################################################
use strict;
use IO::Socket qw(SOMAXCONN);
$| = 1;
my $port = 9999;
my $server_sock = IO::Socket::INET->new(
LocalPort => $port,
Listen => SOMAXCONN,
Proto => 'TCP',
Reuse => 1,
) or die "Kann Socket nicht aufbauen";
print "Warte auf Daten an Port $port\n\n";
while(my $peer = $server_sock->accept){
while(read $peer, my $data, 1024){
print $data;
}
$peer->close;
}
############### client
#!/usr/bin/perl
###########################################################################
# Einfacher Client, der in ein Socket schreibt
###########################################################################
use strict;
use IO::Socket;
my $socket = new IO::Socket::INET (
PeerAddr => 'localhost',
PeerPort => 9999,
Proto => 'tcp'
) or die "Kein Socket bekommen!";
binmode $socket;
print $socket "123456789\n123456789\n123456789\n123456789\n123456789\n123456789\n123456789\n123456789\n";
foreach my $buffer(1..1000){
$socket->print($buffer);
}
$socket->close;
Hallo,
Mehrere Threads machen ja nur dann Sinn, wenn Du mehrere Dinge GLEICHZEITIG (oder "quasi" gleichzeitig) tun willst. Z.b. einen Server, der gleich wieder Connections entgegennehmen kann, während er noch zeichen von einer bereits bestehenden Connection liest.
Willst Du sowas - dann hat Hotti Dir ja schon einen wertvollen Tipp gegeben, wie man das machen kann.
Ansonsten, wenn nur Dein Problem ist, dass <STDIN> zu viele Zeichen liest:
Etwas wie
my @lines = <STDIN>;
funktioniert ja prinzipiell. Nur wird eben der KOMPLETTE Inhalt von STDIN im Speicher gehalten, was man vielleicht bei großen Dateien nicht unbedingt will.
Alternative: Du könntest das z.b mittels read realisieren:
Damit kannst Du eine frei definierbare Anzahl von Zeichen lesen - wie viele hängt davon ab, wieviel Puffer Du dem Programm gönnen willst. Die gelesenen Zeichen gibst Du gleich wieder über den Socket aus. Somit müssen immer nur so viele Zeichen im Speicher gehalten werden, wie du gerade gelesen hast.
Das wiederholst Du dann so lange, bis "read" 0 zurückliefert - dann sind keine Zeichen mehr da, und Du kannst aufhören.
Viele Grüße,
Jörg
hi Jörg, danke fürs Mitmachen,
Alternative: Du könntest das z.b mittels read realisieren:
Damit kannst Du eine frei definierbare Anzahl von Zeichen lesen - wie viele hängt davon ab,
Hier sei mir mal eine klitzkleine Anmerkung erlaubt: read() liest eine Anzahl von _Bytes (nicht Zeichen) aus einem Handle. Ein Byte ist nicht immer gleich ein Zeichen und in der Welt der Binaries gibt es nur noch Bytes, auch wenn diese Bytes Zeichen darstellen könnten.
Seit Perl Unicode unterstützt, wird Perlintern zwischen Bytes und Zeichen unterschieden. Ggf. kann mit dem Pragma use bytes;
die Byte Semantic hergestellt werden, das ist für IO Sachen wichtig, denn in einem Handler (Sockets, STDIN, STDOUT, FileHandler) werden _stets_ Bytes erwartet.
Wenn wir beispielsweise eine utf-8-kodierte Zeichenkette haben, die Perlintern also solche gekennzeichnet ist (Scalar-Value UTF8: SvUTF8) und schicken die mit print auf ein Handle, z.B. STDOUT (Webserver) gibt es eine Fehlermeldung (wide character...) und die Darstellung ist verstümmelt.
Mit use bytes;
vor der print-Anweisung ist die Darstellung ok, eine andere Möglichkeit besteht darin, dem IOLayer die Kodierung mitzuteilen:
binmode STDOUT, ":utf8";
und mit Perl v5.10 kommt use utf8::all;
was das Alles wie von selbst erledigt.
Desweiteren liefert length() entweder die Anzahl der Zeichen oder die Anzahl der Bytes, je nachdem, ob Character- oder Byte Semantics eingestellt ist. Weitere String-Funktionen wie uc(), lc(), substr() funktionieren nur mit Zeichenketten. Bereits mit v5.8. kam das Perlmodul Encode.pm, in dessen POD steht ne ganze Menge zur Problematik...
Also darauf wollte ich nurmalso nebenbei hinweisen ;)
Hotti
Vielleicht kam das bei meinem ersten Text nicht ganz rüber. Im Moment lese ich Daten von der Tastatur mit <STDIN> mit dem Client ein. Diese Daten sende ich mit ->send() über den erstellten Socket zum Server (Server liest mit ->recv() ein). Der Server soll diesen Text fehlerfrei darstellen und gleich wieder zum Client zurücksenden. Und das Problem bei <STDIN> ist ja das nur 1024 oder evtl. auch 2048 Zeichen möglich sind, alles was darüber hinaus geht wird abgeschnitten und nicht zum Server gesendet, so meine Erfahrungen.
Nun sollen aber irgendwas in die 500 ZEILEN Text eingegeben werden können und fehlerfrei zwischen Client - Server -> Server - Client übertragen werden und danach sollte man theoretisch wieder 500 ZEILEN Text eingeben können, <STDIN> wirkt in diesem Fall aber blockierend.
Vielleicht poste ich einfach mal meinen Code ... :) Aber danke schonmal für die Antworten. Ich hoffe meine auskommentierten Testversuche zwischendurch stören nicht zu sehr
-Server-
#!/usr/bin/perl
#tcpserver.pl
use IO::Socket::INET;
# flush after every write
$| = 1;
my ($socket,$client_socket);
my ($peeraddress,$peerport);
# creating object interface of IO::Socket::INET modules which internally does
# socket creation, binding and listening at the specified port address.
$socket = new IO::Socket::INET (
LocalHost => '127.0.0.1',
LocalPort => '5000',
Proto => 'tcp',
Listen => 5,
Reuse => 1
) or die "ERROR in Socket Creation : $!\n";
print "SERVER Waiting for client connection on port 5000\n";
while(1)
{
# waiting for new client connection.
$client_socket = $socket->accept();
# get the host and port number of newly connected client.
$peer_address = $client_socket->peerhost();
$peer_port = $client_socket->peerport();
print "Accepted New Client Connection From : $peer_address, $peer_port\n ";
# write operation on the newly accepted client.
while(1)
{
#$data = "DATA from Server";
# we can also send the data through IO::Socket::INET module,
$client_socket->recv($data,1024);
print "Received from Client : $data\n";
#print $client_socket "$data\n";
$client_socket->send($data);
# read operation on the newly accepted client
#$data = <$client_socket>;
# we can also read from socket through recv() in IO::Socket::INET
}
}
$socket->close();
-Client-
#!/usr/bin/perl
#tcpclient.pl
use IO::Socket::INET;
# flush after every write
$| = 1;
my ($socket,$client_socket);
# creating object interface of IO::Socket::INET modules which internally creates
# socket, binds and connects to the TCP server running on the specific port.
$socket = new IO::Socket::INET (
PeerHost => '127.0.0.1',
PeerPort => '5000',
Proto => 'tcp',
) or die "ERROR in Socket Creation : $!\n";
print "TCP Connection Success.\n";
# read the socket data sent by server.
while(1)
{
#$data = <$socket>;
# we can also read from socket through recv() in IO::Socket::INET
print "Bitte Text eingeben: ";
$data = <STDIN>;
#open(TEXT,"/home/marcel/Desktop/Text.txt") || die $!;
#$data = <TEXT>;
$socket->send($data);
#close(TEXT);
$socket->recv($data,1024);
#$socket->send($data);
#$socket->recv($data,1024);
print "Received from Server : $data\n";
# write on the socket to server.
#$data = "DATA from Client";
#print $socket "$data\n";
}
# we can also send the data through IO::Socket::INET module,
#$socket->send($data);
sleep (10);
$socket->close();
Hallo,
Und das Problem bei <STDIN> ist ja das nur 1024 oder evtl. auch 2048 Zeichen möglich sind, alles was darüber hinaus geht wird abgeschnitten und nicht zum Server gesendet, so meine Erfahrungen.
Nope. <STDIN> liest, wenn die Eingabe nicht abgebrochen wird, bis zum Sankt-Nimmerleins-Tag oder bis der Speicher voll ist (meist tritt letzteres zuerst ein :) ).
Das Abschneiden bei 1024 Zeichen liegt am Aufruf Deiner recv-Methode (sowohl im Server als auch im Client):
$client_socket->recv($data,1024);
print "Received from Client : $data\n";
#print $client_socket "$data\n";
$client_socket->send($data);
"Lies 1024 Zeichen und sende diese zum Server."
Mehr macht das Ding dann nicht.
Da kann natürlich nicht mehr als 1024 bei rumkommen. Du musst das Receive (und zugehörige Send) sooft ausführen, bis da nix mehr kommt. Sozusagen häppchenweise (in 1024 Byte-Blöcken) lesen und gleich wieder rausschreiben.
Viele Grüße,
Jörg
Hallo,
Nun sollen aber irgendwas in die 500 ZEILEN Text eingegeben werden können und fehlerfrei zwischen Client - Server -> Server - Client übertragen werden und danach sollte man theoretisch wieder 500 ZEILEN Text eingeben können, <STDIN> wirkt in diesem Fall aber blockierend.
Die Frage ist: Woran erkennst du das Ende der Eingabe? Zeilenumbruch? Doppelter Zeilenumbruch? ... Dementsprechend musst du die recv()-Funktion und die Konstrukte drumherum gestalten.
Die nächste Frage ist: Woran erkennt der Server das Ende des Datenstroms? Socket zu? Header-Daten? Feste Länge der Daten? Per Kommandozeilenargument?
Grüße
Hallo,
Nun sollen aber irgendwas in die 500 ZEILEN Text eingegeben werden können und fehlerfrei zwischen Client - Server -> Server - Client übertragen werden und danach sollte man theoretisch wieder 500 ZEILEN Text eingeben können, <STDIN> wirkt in diesem Fall aber blockierend.
Die Frage ist: Woran erkennst du das Ende der Eingabe? Zeilenumbruch? Doppelter Zeilenumbruch? ... Dementsprechend musst du die recv()-Funktion und die Konstrukte drumherum gestalten.Die nächste Frage ist: Woran erkennt der Server das Ende des Datenstroms? Socket zu? Header-Daten? Feste Länge der Daten? Per Kommandozeilenargument?
Grüße
Ehrlich gesagt weiss ich das auch noch garnicht genau. Der Laboringenieur hat wohl irgendne Datei die eingelesen wird. Und das sind wohl jede Menge Zeichen. Hatte mir auch schon Gedanken deswegen gemacht, sonst könnte ich ja auch nach nem bestimmten Zeichen suchen lassen und ab dem Zeichen soll das einlesen dann aufhören.
Momentan stehe ich noch bei dem Problem das bei <STDIN> ( ich hab jetzt einfach mal einen Text aus dem Internet kopiert der Zeilenumbrüche enthält) bei diesen besagten Zeilen umbrüchen immer abgeschnitten wird. Das sollte ja eigentlich der chomp() Befehl beheben können, aber so richtig funktioniert das leider noch nicht.
Hier ist nochmal ein Teil der Aufgabenstellung.
1. Client and server will be started on different machines. So, when you start your script you have to give to it parameters (ip adress and port). I suggest you give parameters as arguments. In this case a command for starting of your script will looks like in example
./tcpCl.pl --ip=192.168.1.29 --port=8888
It is just example, and you can give arguments in the way you like.
2. You have to use module "strict". It is a good way how to avoid mistakes.
3. Your scripts have to work with large data. It just means, you can submit on the input of a client 500 lines of text, and all the data must be displayed on the server side, and all the data will received back on the client side.
Momentan stehe ich noch bei dem Problem das bei <STDIN> ( ich hab jetzt einfach mal einen Text aus dem Internet kopiert der Zeilenumbrüche enthält) bei diesen besagten Zeilen umbrüchen immer abgeschnitten wird. Das sollte ja eigentlich der chomp() Befehl beheben können, aber so richtig funktioniert das leider noch nicht.
Nein chomp kann das Problem nicht beheben, es kann nur Zeicchen aus einem String entfernen.
Entweder Du liest die Daten zeilenweise ein, also
while ($zeile=<STDIN>) {
# dies, das und jenes;
}
oder gleich den ganzen Array:
@zeilen=<STDIN>;
oder Du nimmst getc(resource) um die Zeichen eines Datenstroms einzeln zu lesen.
oder Du nimmst read(resource, skalar, 1), das soll sogar schneller sein.
Woran erkennt der Server das Ende des Datenstroms?
Du testest gegen eof oder prüfst ob getc oder read funktioniert haben.
Hoffe, das hilft
fred
Momentan stehe ich noch bei dem Problem das bei <STDIN> ( ich hab jetzt einfach mal einen Text aus dem Internet kopiert der Zeilenumbrüche enthält) bei diesen besagten Zeilen umbrüchen immer abgeschnitten wird. Das sollte ja eigentlich der chomp() Befehl beheben können, aber so richtig funktioniert das leider noch nicht.
Nein chomp kann das Problem nicht beheben, es kann nur Zeicchen aus einem String entfernen.
Entweder Du liest die Daten zeilenweise ein, also
while ($zeile=<STDIN>) {
# dies, das und jenes;
}oder gleich den ganzen Array:
@zeilen=<STDIN>;
oder Du nimmst getc(resource) um die Zeichen eines Datenstroms einzeln zu lesen.
oder Du nimmst read(resource, skalar, 1), das soll sogar schneller sein.Woran erkennt der Server das Ende des Datenstroms?
Du testest gegen eof oder prüfst ob getc oder read funktioniert haben.Hoffe, das hilft
fred
Erst mal Vielen Dank für die zahlreichen Lösungsvorschläge. Ich habe jetzt wirklich versucht sämtliche Lösungsansätze zu testen. Leider haben mich alle nicht wirklich weiter gebracht.
Es muss doch serverseitig möglich sein, die durch recv() begrenzte Zeichenanzahl von 1024 irgendwie zu Puffern. Deshalb ist meine Idee das ich diese Zeichen oder was auch immer in Arrayelementen ablege (puffere) und am Ende wieder mit join() zusammensetze und auf dem Server ausgeben lasse so dass auch wirklich der Text oder was auch immer richtig auf dem Server angezeigt wird.
Im Moment lese ich die Daten im Client mit
while ($data=<STDIN>) {
print ("Bitte Text eingeben: ");
$socket->send($data);
}
aus und lasse sie zum Server senden, wo dann die besagten Probleme auftreten. :(
Ich bin wirklich langsam am verzweifeln.
hi,
Ich bin wirklich langsam am verzweifeln.
IO::File, getlines. Da geht alles raus, was Du eingibst.
Nicht verzweifeln, machen. Wenns geht, besser machen ;)
Hotti
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
use IO::Handle;
print "Mehrzeilige Eingabe, senden mit ^Z in neuer Zeile\n";
my $io = new IO::Handle;
my $sock = IO::Socket::INET->new("localhost:9999") or die "no socket";
$io->fdopen(fileno(STDIN),"r");
print $sock $io->getlines;
$io->close;
close $sock;
Ich komm hier einfach nicht weiter. Kann mir jemand erklären warum der Server die Daten von <STDIN> nicht bekommt ?
#!/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";
}
}
}
#!/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();