# Υποδοχές TCP Μια υποδοχή TCP μεταφέρει μια αξιόπιστη, διατεταγμένη ροή bytes μεταξύ δύο διεργασιών που μπορεί να βρίσκονται σε διαφορετικά μηχανήματα. Αυτή η σελίδα χτίζει τα δύο μισά μιας συνομιλίας TCP από τις χαμηλού επιπέδου ενσωματωμένες συναρτήσεις: έναν **πελάτη** που συνδέεται και ανταλλάσσει μια γραμμή, και έναν **διακομιστή που κάνει fork** που αποδέχεται συνδέσεις και χειρίζεται την καθεμία στο δικό του παιδί. Το άρθρωμα ευκολίας υψηλού επιπέδου `IO::Socket::INET` δεν είναι διαθέσιμο στην PetaPerl, οπότε κάθε συνταγή εδώ χρησιμοποιεί απευθείας το ενσωματωμένο επίπεδο [`socket`](../../p5/core/perlfunc/socket.md) με βοηθούς διευθύνσεων από το άρθρωμα `Socket`. Αυτό δεν είναι απώλεια: η ενσωματωμένη μορφή είναι αυτό που περιτυλίγει το `IO::Socket::INET`, και το να τη βλέπετε ανοιχτά καθιστά τη χειραψία ευανάγνωστη. ## Οι βοηθοί διευθύνσεων Μια διεύθυνση υποδοχής είναι ένα πακεταρισμένο C `struct sockaddr_in`. Το άρθρωμα `Socket` χτίζει και αποσυναρμολογεί αυτή τη δομή για εσάς: ```perl use Socket; my $packed = sockaddr_in($port, $ip); # build my ($port, $ip) = sockaddr_in($packed); # take apart ``` Το `$ip` είναι το ίδιο μια πακεταρισμένη διεύθυνση τεσσάρων bytes. Η `inet_aton` μετατρέπει μια συμβολοσειρά τετράδας με τελείες σε τέτοια· η `inet_ntoa` την επαναφέρει. Η σταθερά `INADDR_LOOPBACK` είναι η πακεταρισμένη μορφή του `127.0.0.1`, και το `INADDR_ANY` σημαίνει «κάθε τοπική διεπαφή». ```perl use Socket; my $ip = inet_aton("127.0.0.1"); print inet_ntoa($ip), "\n"; # 127.0.0.1 ``` ## Ένας πελάτης TCP Ο πελάτης δημιουργεί μια υποδοχή, τη συνδέει σε μια διεύθυνση διακομιστή, και κατόπιν διαβάζει και γράφει τον περιγραφέα όπως κάθε άλλη ροή. ```perl use Socket; my $host = "127.0.0.1"; my $port = 8080; socket(my $sock, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die "socket: $!"; my $addr = sockaddr_in($port, inet_aton($host)); connect($sock, $addr) or die "connect: $!"; $sock->autoflush(1); # send each line as it is printed print $sock "ping\n"; my $reply = <$sock>; print "server said: $reply"; # server said: pong: ping close $sock; ``` Η μία γραμμή που πιάνει τον καθένα είναι το `$sock->autoflush(1)`. Χωρίς αυτό, το `print $sock "ping\n"` παραμένει στον ενταμιευτή εξόδου της Perl, ο διακομιστής δεν το βλέπει ποτέ, και οι δύο πλευρές μπλοκάρουν για πάντα περιμένοντας η μία την άλλη. Σε κάθε υποδοχή όπου γράφετε μια αίτηση και μετά διαβάζετε μια απάντηση, ορίστε autoflush πριν από την πρώτη εγγραφή. ## Ένας διακομιστής που κάνει fork Ένας διακομιστής δένεται σε μια θύρα, ακούει, και κατόπιν επαναλαμβάνει αποδεχόμενος συνδέσεις. Η κλασική δομή χειρίζεται κάθε πελάτη σε ένα παιδί που έχει δημιουργηθεί με fork, ώστε ένας αργός πελάτης να μην μπορεί να καθυστερήσει τον επόμενο: ```perl use Socket; use POSIX qw(WNOHANG); # for the non-blocking reaper my $port = 8080; my $backlog = 128; # pending-connection queue depth socket(my $server, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die "socket: $!"; # Let the address be reused immediately after a restart. setsockopt($server, SOL_SOCKET, SO_REUSEADDR, 1) or die "setsockopt: $!"; bind($server, sockaddr_in($port, INADDR_ANY)) or die "bind: $!"; listen($server, $backlog) or die "listen: $!"; # Reap finished children so they do not become zombies. $SIG{CHLD} = sub { 1 while waitpid(-1, WNOHANG) > 0 }; print "listening on port $port\n"; while (accept(my $client, $server)) { my $pid = fork(); die "fork: $!" unless defined $pid; if ($pid == 0) { # Child: serve this one connection, then exit. close $server; # child does not need the listener $client->autoflush(1); while (my $line = <$client>) { print $client "pong: $line"; } close $client; exit 0; } # Parent: hand the connection to the child and move on. close $client; # parent does not need this client } ``` Τρεις λεπτομέρειες κάνουν αυτόν τον διακομιστή σωστό αντί απλώς λειτουργικό: - **`SO_REUSEADDR` πριν από το `bind`.** Αφού ένας διακομιστής τερματίσει, ο πυρήνας κρατά τη θύρα σε `TIME_WAIT` για κάποιο διάστημα. Χωρίς αυτή την επιλογή, μια επανεκκίνηση αποτυγχάνει με «Address already in use». Ορίστε την και η επανεκκίνηση δένεται αμέσως. - **Ο συγκομιστής `$SIG{CHLD}`.** Κάθε παιδί που δημιουργείται με fork γίνεται zombie όταν τερματίζει, μέχρι ο γονέας να καλέσει [`waitpid`](../../p5/core/perlfunc/waitpid.md). Ο χειριστής συγκομίζει όλα τα ολοκληρωμένα παιδιά χωρίς μπλοκάρισμα με `WNOHANG`, ώστε ο βρόχος αποδοχής να μην σταματά ποτέ για να περιμένει. - **Και οι δύο πλευρές κλείνουν τον περιγραφέα που δεν χρησιμοποιούν.** Το παιδί κλείνει την υποδοχή που ακούει· ο γονέας κλείνει την αποδεκτή υποδοχή πελάτη. Αν κάποια από τις δύο παραλείψει το `close` της, ο υποκείμενος περιγραφέας αρχείου παραμένει ανοιχτός στη λάθος διεργασία και η σύνδεση δεν βλέπει ποτέ EOF. ## Εκτέλεση και των δύο μισών μαζί Για μια αυτοτελή επίδειξη, κάντε fork τον διακομιστή στο παρασκήνιο και εκτελέστε τον πελάτη στον γονέα. Ο διακομιστής δένεται σε μια εφήμερη θύρα (θύρα `0`) και αναφέρει τη θύρα που ανέθεσε ο πυρήνας: ```perl use Socket; # --- server child on an ephemeral port --- socket(my $server, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die "socket: $!"; setsockopt($server, SOL_SOCKET, SO_REUSEADDR, 1) or die "setsockopt: $!"; bind($server, sockaddr_in(0, INADDR_LOOPBACK)) or die "bind: $!"; listen($server, 128) or die "listen: $!"; my ($port) = sockaddr_in(getsockname($server)); my $kid = fork() // die "fork: $!"; if ($kid == 0) { accept(my $client, $server) or die "accept: $!"; $client->autoflush(1); my $line = <$client>; print $client "pong: $line"; close $client; exit 0; } # --- client in the parent --- socket(my $sock, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die; connect($sock, sockaddr_in($port, INADDR_LOOPBACK)) or die "connect: $!"; $sock->autoflush(1); print $sock "ping\n"; print "client got: ", scalar <$sock>; # client got: pong: ping close $sock; waitpid($kid, 0); ``` Η `getsockname($server)` αναφέρει την τοπική διεύθυνση στην οποία είναι δεμένη η υποδοχή. Το αποπακετάρισμά της με `sockaddr_in` ανακτά τη θύρα που επέλεξε ο πυρήνας, στην οποία στη συνέχεια συνδέεται ο πελάτης - ο τυπικός τρόπος να γράψετε ένα test που δεν ενσωματώνει σταθερά μια θύρα. ## Διασταυρούμενοι σύνδεσμοι αναφοράς Κάθε κλήση έχει μια σελίδα αναφοράς ανά ενσωματωμένη συνάρτηση με την πλήρη μορφή ορισμάτων και την τιμή επιστροφής: - [`socket`](../../p5/core/perlfunc/socket.md) - δημιουργεί το άκρο - [`bind`](../../p5/core/perlfunc/bind.md) - το προσαρτά σε μια τοπική διεύθυνση - [`listen`](../../p5/core/perlfunc/listen.md) - το σημειώνει ως αποδεχόμενο συνδέσεις - [`accept`](../../p5/core/perlfunc/accept.md) - παίρνει την επόμενη εκκρεμή σύνδεση - [`connect`](../../p5/core/perlfunc/connect.md) - προσεγγίζει έναν διακομιστή - [`setsockopt`](../../p5/core/perlfunc/setsockopt.md) - ορίζει το `SO_REUSEADDR` και τα συναφή - [`getsockname`](../../p5/core/perlfunc/getsockname.md) - ανακαλύπτει τη δεμένη θύρα - [`fork`](../../p5/core/perlfunc/fork.md) και [`waitpid`](../../p5/core/perlfunc/waitpid.md) - δημιουργεί και συγκομίζει το παιδί ανά πελάτη - [`%SIG`](../../p5/core/perlvar/signals.md) - ο χειριστής συγκομιδής `CHLD` ## Επόμενο Για υποδοχές ροής μόνο τοπικές χωρίς τη στοίβα δικτύου, και για datagrams χωρίς σύνδεση, δείτε [Υποδοχές UNIX-domain και UDP](sockets-unix-udp.md).