Υποδοχές Udp. Χρήση υποδοχών για εργασία με UDP. Σχέδιο αλληλεπίδρασης πρωτοκόλλων δικτύου

Τελευταία ενημέρωση: 31.10.2015

Το πρωτόκολλο UDP δεν απαιτεί μόνιμη σύνδεση και για πολλούς ανθρώπους μπορεί να είναι πιο εύκολο να εργαστούν με το UDP παρά με το TCP. Οι περισσότερες από τις αρχές κατά την εργασία με το UDP είναι οι ίδιες όπως και με το TCP.

Αρχικά, δημιουργείται μια υποδοχή:

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

Εάν η υποδοχή πρέπει να λαμβάνει μηνύματα, τότε πρέπει να τη συνδέσετε σε μια τοπική διεύθυνση και σε μία από τις θύρες χρησιμοποιώντας τη μέθοδο Bind:

IPEndPoint localIP = νέο IPEndPoint(IPAddress.Parse("127.0.0.1"), 5555); socket.Bind(localIP);

Μετά από αυτό, μπορείτε να στέλνετε και να λαμβάνετε μηνύματα. Για να λάβετε μηνύματα, χρησιμοποιήστε τη μέθοδο ReceiveFrom():

Byte data = νέο byte. // buffer για τα ληφθέντα δεδομένα //διεύθυνση από την οποία προήλθαν τα δεδομένα EndPoint remoteIp = new IPEndPoint(IPAddress.Any, 0); int bytes = socket.ReceiveFrom(data, ref remoteIp);

Ως παράμετρος, η μέθοδος μεταβιβάζεται σε έναν πίνακα byte στον οποίο πρέπει να διαβάζονται τα δεδομένα και στο απομακρυσμένο σημείο από το οποίο προέρχονται αυτά τα δεδομένα. Η μέθοδος επιστρέφει τον αριθμό των byte που διαβάστηκαν.

Για να στείλετε δεδομένα, χρησιμοποιήστε τη μέθοδο SendTo():

Μήνυμα συμβολοσειράς = Console.ReadLine(); byte data = Encoding.Unicode.GetBytes(message); EndPoint remotePoint = νέο IPEndPoint(IPAddress.Parse("127.0.0.1"), remotePort); listeningSocket.SendTo(data, remotePoint);

Μια σειρά δεδομένων προς αποστολή, καθώς και η διεύθυνση στην οποία πρέπει να σταλούν αυτά τα δεδομένα, μεταβιβάζεται στη μέθοδο.

Ας δημιουργήσουμε ένα πρόγραμμα πελάτη UDP:

Χρήση του συστήματος. χρησιμοποιώντας System.Text; χρησιμοποιώντας System.Threading.Tasks; χρησιμοποιώντας System.Net; χρησιμοποιώντας System.Net.Sockets; namespace SocketUdpClient ( class Program ( static int localPort; // θύρα για λήψη μηνυμάτων static int remotePort; // θύρα για αποστολή μηνυμάτων static Socket listeningSocket; static void Main(string args) ( Console.Write("Εισαγάγετε τη θύρα για λήψη μηνυμάτων: "). στείλτε μηνύματα, πληκτρολογήστε μήνυμα και πατήστε Enter"); Console.WriteLine(); δοκιμάστε ( listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); Task listeningTask = new Task(Listen); listeningTask.Start( ) // αποστολή μηνυμάτων σε διαφορετικές θύρες ενώ (true) (Μήνυμα συμβολοσειράς = Console.ReadLine(); δεδομένα byte = Encoding.Unicode.GetBytes (μήνυμα); EndPoint remotePoint = new IPEndPoint(IPAddress.Parse("127.0. 0.1"), remotePort); listeningSocket.SendTo(data, remotePoint); ) catch (Exception ex) ( Console.WriteLine(ex.Message);

Αρχικά, ο χρήστης εισάγει τις θύρες για λήψη δεδομένων και για αποστολή. Υποτίθεται ότι δύο εφαρμογές πελάτη που θα αλληλεπιδρούν μεταξύ τους εκτελούνται στον ίδιο τοπικό υπολογιστή. Εάν οι διευθύνσεις πελατών είναι διαφορετικές, μπορείτε επίσης να καταχωρίσετε τη διεύθυνση για την αποστολή δεδομένων.

Μετά την είσοδο στις θύρες, ξεκινά η εργασία ακρόασης εισερχόμενων μηνυμάτων. Σε αντίθεση με τον διακομιστή tcp, δεν χρειάζεται να καλέσετε τις μεθόδους Ακρόαση και Αποδοχή. ΣΕ ατέρμων βρόχοςμπορούμε να λάβουμε απευθείας δεδομένα χρησιμοποιώντας τη μέθοδο ReceiveFrom(), η οποία μπλοκάρει το νήμα που καλεί μέχρι να φτάσει το επόμενο κομμάτι δεδομένων.

Αυτή η μέθοδος επιστρέφει, μέσω μιας παραμέτρου ref, το απομακρυσμένο σημείο από το οποίο ελήφθησαν τα δεδομένα:

IPEndPoint remoteFullIp = remoteIp ως IPEndPoint.

Δηλαδή, παρά το γεγονός ότι σε αυτήν την περίπτωση η λήψη και η αποστολή μηνυμάτων διαφοροποιούνται και ο τρέχων πελάτης στέλνει δεδομένα μόνο στη θύρα που εισήχθη αρχικά, μπορούμε εύκολα να προσθέσουμε τη δυνατότητα απάντησης σε μηνύματα χρησιμοποιώντας τα δεδομένα του απομακρυσμένου σημείου που λάβαμε ( διεύθυνση και λιμάνι).

Το κύριο νήμα στέλνει μηνύματα χρησιμοποιώντας τη μέθοδο SendTo().

Έτσι, η εφαρμογή εκτελεί άμεσα τις λειτουργίες τόσο του διακομιστή όσο και του πελάτη.

Τώρα ας ξεκινήσουμε δύο αντίγραφα της εφαρμογής και ας εισαγάγουμε διαφορετικά δεδομένα για τις θύρες. Πρώτος πελάτης:

Εισαγάγετε τη θύρα λήψης μηνυμάτων: 4004 Εισαγάγετε τη θύρα για την αποστολή μηνυμάτων: 4005 Για να στείλετε μηνύματα, πληκτρολογήστε ένα μήνυμα και πατήστε Enter 127.0.0.1:4005 - hello port 4004 καλημέρα, θύρα 4005 υπέροχο καιρό

Δεύτερος πελάτης:

Εισαγάγετε τη θύρα λήψης μηνυμάτων: 4005 Εισαγάγετε τη θύρα για την αποστολή μηνυμάτων: 4004 Για να στείλετε μηνύματα, πληκτρολογήστε ένα μήνυμα και πατήστε Enter hello port 4004 127.0.0.1:4004 - καλημέρα, θύρα 4005 127.0.0.1:4004 - υπέροχος καιρός

Ήρθε η ώρα να χρησιμοποιήσετε το Erlang για τον προορισμό του - να εφαρμόσετε μια υπηρεσία δικτύου. Τις περισσότερες φορές, τέτοιες υπηρεσίες γίνονται με βάση έναν διακομιστή web, πάνω από το πρωτόκολλο HTTP. Αλλά θα πάρουμε το παρακάτω επίπεδο - υποδοχές TCP και UDP.

Υποθέτω ότι γνωρίζετε ήδη πώς λειτουργεί το δίκτυο, τι είναι το Πρωτόκολλο Διαδικτύου, το Πρωτόκολλο Δεδομένων Χρήστη και το Πρωτόκολλο Ελέγχου Μετάδοσης. Αυτό το θέμα είναι γνωστό στους περισσότερους προγραμματιστές. Αλλά αν για κάποιο λόγο το χάσατε, θα πρέπει πρώτα να προλάβετε και μετά να επιστρέψετε σε αυτό το μάθημα.

Υποδοχή UDP

Ας θυμηθούμε μέσα γενικό περίγραμματι είναι το UDP:

  • πρωτόκολλο μεταφοράς σύντομων μηνυμάτων (Datagram).
  • γρήγορη παράδοση?
  • καμία μόνιμη σύνδεση μεταξύ πελάτη και διακομιστή, ανιθαγενής.
  • Η παράδοση μηνυμάτων και η παραγγελία παράδοσης δεν είναι εγγυημένες.

Για εργασία με UDP, χρησιμοποιείται η λειτουργική μονάδα gen_udp.

Ας ξεκινήσουμε δύο κόμβους και ας δημιουργήσουμε επικοινωνία μεταξύ τους.

Στον 1ο κόμβο, ανοίξτε το UDP στη θύρα 2000:

1> (ok, Socket) = gen_udp:open(2000, ). (εντάξει, #Port<0.587>}

Κλήση gen_udp:open/2, περνάμε τον αριθμό θύρας και μια λίστα επιλογών. Η λίστα με όλες τις πιθανές επιλογές είναι αρκετά μεγάλη, αλλά μας ενδιαφέρουν δύο από αυτές:

δυάδικος-- η πρίζα ανοίγει σε δυαδική λειτουργία. Εναλλακτικά, η πρίζα μπορεί να ανοίξει λειτουργία κειμένουκαθορίζοντας την επιλογή λίστα. Η διαφορά είναι πώς ερμηνεύουμε τα δεδομένα που λαμβάνονται από την υποδοχή - ως ροή byte ή ως κείμενο.

(ενεργό, αληθινό)-- η υποδοχή είναι ανοιχτή σε ενεργή λειτουργία, πράγμα που σημαίνει ότι τα δεδομένα που φτάνουν στην υποδοχή θα αποστέλλονται ως μηνύματα στο γραμματοκιβώτιο του νήματος στο οποίο ανήκει η υποδοχή. Περισσότερα για αυτό παρακάτω.

Στον 2ο κόμβο, ανοίξτε το UDP στη θύρα 2001:

1> (ok, Socket) = gen_udp:open(2001, ). (εντάξει, #Port<0.587>}

Και θα στείλουμε ένα μήνυμα από τον 1ο κόμβο στον 2ο:

2> gen_udp:send(Socket, (127,0,0,1), 2001,<<"Hello from 2000">>). Εντάξει

Κλήση gen_udp:send/4, μεταδίδουμε την υποδοχή, τη διεύθυνση και τη θύρα του παραλήπτη και το ίδιο το μήνυμα.

Η διεύθυνση μπορεί να είναι όνομα τομέαως συμβολοσειρά ή άτομο, ή διεύθυνση IPv4 ως πλειάδα 4 αριθμών ή διεύθυνση IPv6 ως πλειάδα 8 αριθμών.

Στον 2ο κόμβο θα βεβαιωθούμε ότι έχει φτάσει το μήνυμα:

2> <0.587>,{127,0,0,1},2000,<<"Hello from 2000">>) εντάξει

Το μήνυμα φτάνει ως πλειάδα (udp, Socket, SenderAddress, SenderPort, Packet).

Ας στείλουμε ένα μήνυμα από τον 2ο κόμβο στον 1ο:

3> gen_udp:send(Socket, (127,0,0,1), 2000,<<"Hello from 2001">>). Εντάξει

Στον 1ο κόμβο, θα βεβαιωθούμε ότι έχει φτάσει το μήνυμα:

3> flush(). Η Shell πήρε (udp,#Port<0.587>,{127,0,0,1},2001,<<"Hello from 2001">>) εντάξει

Όπως μπορείτε να δείτε, όλα είναι απλά εδώ.

Ενεργητική και παθητική λειτουργία υποδοχής

ΚΑΙ gen_udp, Και gen_tcp, και τα δύο έχουν ένα σημαντική ρύθμιση: τρόπος εργασίας με εισερχόμενα δεδομένα. Αυτή μπορεί να είναι είτε ενεργή λειτουργία (ενεργό, αληθινό), ή παθητική λειτουργία (ενεργό, ψευδές).

Στην ενεργή λειτουργία, ένα νήμα λαμβάνει εισερχόμενα πακέτα ως μηνύματα στο εσωτερικό του γραμματοκιβώτιο. Και μπορούν να ληφθούν και να υποβληθούν σε επεξεργασία μέσω κλήσης λήψης, όπως όλα τα άλλα μηνύματα.

Για μια υποδοχή udp αυτά είναι μηνύματα όπως:

(udp, Socket, SenderAddress, SenderPort, Packet)

τα έχουμε δει ήδη:

(udp,#Port<0.587>,{127,0,0,1},2001,<<"Hello from 2001">>}

Για μια υποδοχή tcp παρόμοια μηνύματα:

(tcp, Socket, Packet)

Η ενεργή λειτουργία είναι εύκολη στη χρήση, αλλά επικίνδυνη επειδή ο πελάτης μπορεί να υπερχειλίσει την ουρά μηνυμάτων του νήματος, να εξαντληθεί η μνήμη και να διακοπεί ο κόμβος. Επομένως, συνιστάται η παθητική λειτουργία.

ΣΕ παθητική λειτουργίαπρέπει να λάβετε τα δεδομένα μόνοι σας καλώντας gen_udp:recv/3Και gen_tcp:recv/3:

Gen_udp:recv(Socket, Length, Timeout) -> (ok, (Διεύθυνση, Θύρα, Πακέτο)) | (σφάλμα, Λόγος) gen_tcp:recv(Socket, Length, Timeout) -> (ok, Packet) | (λάθος, λόγος)

Εδώ υποδεικνύουμε πόσα byte δεδομένων θέλουμε να διαβάσουμε από την υποδοχή. Εάν υπάρχουν αυτά τα δεδομένα, τότε τα λαμβάνουμε αμέσως. Εάν όχι, η κλήση μπλοκάρεται μέχρι να φτάσουν αρκετά δεδομένα. Μπορείτε να καθορίσετε το Timeout για να αποφύγετε τον αποκλεισμό του νήματος για μεγάλο χρονικό διάστημα.

Ωστόσο, gen_udp:recvαγνοεί το όρισμα Length και επιστρέφει όσα δεδομένα υπάρχουν στην υποδοχή. Ή μπλοκάρει και περιμένει κάποια δεδομένα αν δεν υπάρχει τίποτα στην πρίζα. Δεν είναι σαφές γιατί το όρισμα Length υπάρχει καθόλου στο API.

Για gen_tcp:recvτο όρισμα Length λειτουργεί όπως αναμένεται. Εκτός αν έχει καθοριστεί η επιλογή (πακέτο, μέγεθος), το οποίο θα συζητηθεί παρακάτω.

Υπάρχει ακόμα μια επιλογή (ενεργό, μία φορά). Σε αυτήν την περίπτωση, η πρίζα ξεκινά σε ενεργή λειτουργία, λαμβάνει το πρώτο πακέτο δεδομένων ως μήνυμα και μεταβαίνει αμέσως σε παθητική λειτουργία.

Υποδοχή TCP

Ας θυμηθούμε γενικά τι είναι το TCP:

  • Το αξιόπιστο πρωτόκολλο μεταφοράς δεδομένων εγγυάται την παράδοση μηνυμάτων και την παραγγελία παράδοσης.
  • μόνιμη σύνδεση μεταξύ πελάτη και διακομιστή, έχει κατάσταση.
  • πρόσθετο κόστος για τη δημιουργία και το κλείσιμο συνδέσεων και τη μεταφορά δεδομένων.

Πρέπει να σημειωθεί ότι χρειάζεται πολύς χρόνος για να διατηρηθεί επίμονες συνδέσειςμε πολλές χιλιάδες πελάτες είναι ακριβό. Όλες οι συνδέσεις πρέπει να λειτουργούν ανεξάρτητα η μία από την άλλη, που σημαίνει σε διαφορετικά νήματα. Για πολλές γλώσσες προγραμματισμού (αλλά όχι Erlang) αυτό είναι ένα σοβαρό πρόβλημα.

Γι' αυτό είναι τόσο δημοφιλές Πρωτόκολλο HTTP, το οποίο, αν και λειτουργεί σε μια υποδοχή TCP, συνεπάγεται σύντομο χρόνο αλληλεπίδρασης. Αυτό επιτρέπει σε έναν σχετικά μικρό αριθμό νημάτων (δεκάδες-εκατοντάδες) να εξυπηρετούν σημαντικά μεγαλύτερο αριθμόπελάτες (χιλιάδες, δεκάδες χιλιάδες).

Σε ορισμένες περιπτώσεις, εξακολουθεί να υπάρχει ανάγκη για μακροχρόνιες μόνιμες συνδέσεις μεταξύ του πελάτη και του διακομιστή. Για παράδειγμα, για συνομιλίες ή για παιχνίδια για πολλούς παίκτες. Και εδώ η Erlang έχει λίγους ανταγωνιστές.

Για να εργαστείτε με το TCP, χρησιμοποιείται η λειτουργική μονάδα gen_tcp.

Η εργασία με μια υποδοχή TCP είναι πιο δύσκολη από την εργασία με μια υποδοχή UDP. Τώρα έχουμε ρόλους πελάτη και διακομιστή που απαιτούν διαφορετικές υλοποιήσεις. Ας εξετάσουμε μια επιλογή υλοποίησης διακομιστή.

Ενότητα (διακομιστής).<- lists:seq(1, 5)], timer:sleep(infinity), ok. accept(Id, ListenSocket) ->io:format("Socket #~p αναμονή για client~n", ), (ok, _Socket) = gen_tcp:accept(ListenSocket), io:format("Socket #~p, session start~n", ), handle_connection (Id, ListenSocket).

handle_connection(Id, ListenSocket) -> λήψη (tcp, Socket, Msg) -> io:format("Socket #~p πήρε μήνυμα: ~p~n", ), gen_tcp:send(Socket, Msg), handle_connection(Id , ListenSocket); (tcp_closed, _Socket) ->Και Υπάρχουν δύο τύποι πρίζας:Ακούστε Socket

Αποδοχή υποδοχής . Υπάρχει μόνο ένα Listen Socket, δέχεται όλα τα αιτήματα σύνδεσης. Χρειάζεστε πολλά Accept Sockets, ένα για κάθε σύνδεση. Το νήμα που δημιουργεί την πρίζα γίνεται ο ιδιοκτήτης της πρίζας. Εάν εξέλθει το νήμα κατόχου, η υποδοχή κλείνει αυτόματα. Επομένως, δημιουργούμε ένα ξεχωριστό νήμα για κάθε υποδοχή.Το Listen Socket πρέπει να εκτελείται πάντα και για να γίνει αυτό, το νήμα κατόχου του δεν πρέπει να τερματίζεται. Επομένως σε διακομιστής/1προσθέσαμε μια πρόκληση

χρονόμετρο: ύπνος (άπειρο) . Αυτό θα μπλοκάρει το νήμα και θα αποτρέψει το τελείωμα του. Αυτή η εφαρμογή είναι, φυσικά, εκπαιδευτική. Καλό θα ήταν να παρέχουμε τη δυνατότητα σωστής διακοπής του διακομιστή, αλλά αυτό δεν είναι δυνατό εδώ.Το Accept Socket και το νήμα για αυτό θα μπορούσαν να δημιουργηθούν δυναμικά καθώς εμφανίζονται οι πελάτες. Αρχικά, μπορείτε να δημιουργήσετε ένα τέτοιο νήμα και να καλέσετε gen_tcp:accept/1και περιμένετε τον πελάτη. Αυτή η κλήση αποκλείεται. Τελειώνει όταν εμφανιστεί ο πελάτης. Μπορείτε να συνεχίσετε να σερβίρετε

τρέχον πελάτη

σε αυτό το νήμα και δημιουργήστε ένα νέο νήμα περιμένοντας έναν νέο πελάτη. Εδώ όμως έχουμε διαφορετική εφαρμογή. Δημιουργούμε μια ομάδα πολλών νημάτων εκ των προτέρων και όλα περιμένουν πελάτες. Μετά την ολοκλήρωση της εργασίας με έναν πελάτη, η πρίζα δεν είναι κλειστή, αλλά περιμένει μια νέα. Έτσι, αντί να ανοίγουμε συνεχώς νέες πρίζες και να κλείνουμε παλιές, χρησιμοποιούμε μια πισίνα με πρίζες μεγάλης διάρκειας.Είναι πιο αποτελεσματικό όταν

μεγάλες ποσότητες πελάτες. Πρώτον, γιατί δεχόμαστε συνδέσεις πιο γρήγορα. Δεύτερον, λόγω του γεγονότος ότι διαχειριζόμαστε πιο προσεκτικά τις πρίζες ως πόρο του συστήματος.Τα νήματα ανήκουν σε έναν κόμβο Erlang και μπορούμε να δημιουργήσουμε όσα από αυτά θέλουμε. Αλλά οι πρίζες ανήκουν στο λειτουργικό σύστημα. Ο αριθμός τους είναι περιορισμένος, αν και αρκετά μεγάλος. (Μιλάμε για όριο στον αριθμό

Το μέγεθος της πισίνας μας είναι σε μέγεθος παιχνιδιού - 5 ζεύγη ρέματος-πρίζας. Στην πραγματικότητα, χρειαζόμαστε μια δεξαμενή πολλών εκατοντάδων τέτοιων ζευγαριών. Θα ήταν επίσης ωραίο να μπορείτε να αυξάνετε και να μειώνετε αυτό το pool κατά το χρόνο εκτέλεσης προκειμένου να προσαρμοστείτε στο τρέχον φορτίο.

Η τρέχουσα συνεδρία με τον πελάτη υποβάλλεται σε επεξεργασία στη συνάρτηση handle_connection/2. Μπορεί να φανεί ότι η υποδοχή είναι σε ενεργή λειτουργία και το νήμα λαμβάνει μηνύματα όπως (tcp, Socket, Msg), Πού Msg-- αυτά είναι δυαδικά δεδομένα που προέρχονται από τον πελάτη. Στέλνουμε αυτά τα δεδομένα πίσω στον πελάτη, δηλαδή υλοποιούμε μια μπανάλ υπηρεσία ηχούς :)

Όταν ο πελάτης κλείνει τη σύνδεση, το νήμα λαμβάνει ένα μήνυμα (tcp_closed, _Socket), επιστρέφει στο αποδέχομαι/2και περιμένει τον επόμενο πελάτη.

Έτσι μοιάζει η λειτουργία ενός τέτοιου διακομιστή με δύο πελάτες telnet:

$ telnet localhost 1234 Προσπάθεια 127.0.0.1... Συνδέθηκε με localhost. Ο χαρακτήρας διαφυγής είναι "^]". γεια από τον πελάτη 1 γεια από τον πελάτη 1 κάποιο μήνυμα από τον πελάτη 1 κάποιο μήνυμα από τον πελάτη 1 νέο μήνυμα από τον πελάτη 1 νέο μήνυμα από τον πελάτη 1 πελάτη 1 πρόκειται να κλείσει τη σύνδεση πελάτης 1 πρόκειται να κλείσει τη σύνδεση ^] telnet> έξοδος Η σύνδεση έκλεισε.

$ telnet localhost 1234 Προσπάθεια 127.0.0.1... Συνδέθηκε με localhost. Ο χαρακτήρας διαφυγής είναι "^]". γεια από τον πελάτη 2 γεια από τον πελάτη 2 μήνυμα από τον πελάτη 2 μήνυμα από τον πελάτη 2 ο πελάτης 2 είναι ακόμα ενεργός πελάτης 2 είναι ακόμα ενεργός αλλά ο πελάτης 2 είναι ακόμα ενεργός αλλά ο πελάτης 2 είναι ακόμα ενεργός και τώρα ο πελάτης 2 πρόκειται να κλείσει τη σύνδεση και τώρα ο πελάτης 2 πρόκειται να κλείσει τη σύνδεση ^] telnet> κλείσιμο Η σύνδεση έκλεισε.

2> διακομιστής:start(). έναρξη διακομιστή στη θύρα 1234 ok Υποδοχή #1 αναμονή για πελάτη Υποδοχή #2 αναμονή για πελάτη Υποδοχή #3 αναμονή για πελάτη Υποδοχή #4 αναμονή για πελάτη Υποδοχή #5 αναμονή για πελάτη Υποδοχή #1, έναρξη περιόδου λειτουργίας Η υποδοχή #1 έλαβε μήνυμα:<<"hello from client 1\r\n">> Η υποδοχή #1 έλαβε μήνυμα:<<"some message from client 1\r\n">> Υποδοχή #2, έναρξη περιόδου λειτουργίας Η υποδοχή #2 έλαβε μήνυμα:<<"hello from client 2\r\n">> Η υποδοχή #2 έλαβε μήνυμα:<<"message from client 2\r\n">> Η υποδοχή #1 έλαβε μήνυμα:<<"new message from client 1\r\n">> Η υποδοχή #2 έλαβε μήνυμα:<<"client 2 is still active\r\n">> Η υποδοχή #1 έλαβε μήνυμα:<<"client 1 is going to close connection\r\n">> Υποδοχή #1, περίοδος λειτουργίας κλειστή Η υποδοχή #1 αναμονή για τον πελάτη Η υποδοχή #2 έλαβε μήνυμα:<<"but client 2 is still active\r\n">> Η υποδοχή #2 έλαβε μήνυμα:<<"and now client 2 is going to close connection\r\n">> Υποδοχή #2, περίοδος λειτουργίας κλειστή Υποδοχή #2 αναμονή για πελάτη

Διακομιστής σε παθητική λειτουργία

Όλα αυτά είναι καλά, αλλά καλός διακομιστήςπρέπει να λειτουργεί σε παθητική λειτουργία. Δηλαδή, θα πρέπει να λαμβάνει δεδομένα από τον πελάτη όχι με τη μορφή μηνυμάτων στο γραμματοκιβώτιο, αλλά με κλήση gen_tcp:recv/2,3.

Η απόχρωση είναι ότι εδώ πρέπει να υποδείξουμε πόσα δεδομένα θέλουμε να διαβάσουμε. Πώς μπορεί ο διακομιστής να γνωρίζει πόσα δεδομένα του έστειλε ο πελάτης; Λοιπόν, προφανώς, ο ίδιος ο πελάτης πρέπει να πει πόσα δεδομένα πρόκειται να στείλει. Για να γίνει αυτό, ο πελάτης στέλνει πρώτα ένα μικρό πακέτο υπηρεσιών, στο οποίο υποδεικνύει το μέγεθος των δεδομένων του και στη συνέχεια στέλνει ο ίδιος τα δεδομένα.

Τώρα πρέπει να αποφασίσουμε πόσα byte θα πρέπει να καταλαμβάνει αυτό το πακέτο υπηρεσιών. Εάν είναι 1 byte, τότε δεν μπορείτε να συσκευάσετε έναν αριθμό μεγαλύτερο από 255, μπορείτε να συσκευάσετε τον αριθμό 65535 σε 2 byte και το 4294967295 σε 4 byte δεν είναι αρκετό. Είναι πιθανό ότι ο πελάτης θα χρειαστεί να στείλει περισσότερα από 255 byte δεδομένων. Μια κεφαλίδα 2 byte είναι μια χαρά. Μερικές φορές χρειάζεται μια κεφαλίδα 4 byte.

Έτσι, ο πελάτης στέλνει ένα πακέτο υπηρεσιών 2 byte που υποδεικνύει πόσα δεδομένα θα το ακολουθήσουν και, στη συνέχεια, τα ίδια τα δεδομένα:

Μήνυμα =<<"Hello">>, Μέγεθος = byte_size(Msg), Κεφαλίδα =<>, gen_tcp:send(Socket,<

>),

Πλήρης κωδικός πελάτη:

Ενότητα (πελάτης 2).<-εξαγωγή(). start() -> start("localhost", 1234).<

start(Host, Port) -> spawn(?MODULE, client, ). αποστολή (Pid, Msg) -> Pid ! (αποστολή, μήνυμα), εντάξει. stop(Pid) -> Pid ! σταμάτα, εντάξει.

client(Host, Port) -> io:format("Client ~p connects to ~p:~p~n", ), (ok, Socket) = gen_tcp:connect(Host, Port, ), loop(Socket).

loop(Socket) -> λήψη (send, Msg) -> io:format("Client ~p send ~p~n", ), Size = byte_size(Msg), Header =<>, gen_tcp:send(Socket,

>), βρόχος (Socket); (tcp, Socket, Msg) -> io:format("Client ~p got message: ~p~n", ), loop(Socket);Και stop -> io:format("Client ~p κλείνεισύνδεση και

stops~n", ), gen_tcp:close(Socket) μετά από 200 -> loop(Socket) end.<- lists:seq(1, 5)], timer:sleep(infinity), ok. accept(Id, ListenSocket) ->Ο διακομιστής διαβάζει πρώτα 2 byte, καθορίζει το μέγεθος των δεδομένων και στη συνέχεια διαβάζει όλα τα δεδομένα:<> = Κεφαλίδα, (ok, Msg) = gen_tcp:recv(Socket, Size), io:format("Socket #~p πήρε μήνυμα: ~p~n", ), gen_tcp:send(Socket, Msg), handle_connection( Id, ListenSocket, Socket);

(σφάλμα, κλειστό) -> io:format("Socket #~p, session κλειστή ~n", ), accept(Id, ListenSocket) end.

Ένα παράδειγμα μιας συνεδρίας από την πλευρά του πελάτη:<0.40.0>2> Pid = client2:start(). Πελάτης<0.40.0>συνδέεται στο "localhost":1234<<"Hello">3> client2:send(Pid,<0.40.0>>). Πελάτης<<"Hello">στέλνω<0.40.0>> εντάξει πελάτη<<"Hello">έλαβα μήνυμα:<<"Hello again">3> client2:send(Pid,<0.40.0>>). Πελάτης<<"Hello again">στέλνω<0.40.0>> εντάξει πελάτη<<"Hello again">> 4> client2:send(Pid,<0.40.0>> 5> client2:stop(Pid). Πελάτης

κλείνει τη σύνδεση και σταματά εντάξει

Και από την πλευρά του διακομιστή:<<"Hello">> Η υποδοχή #1 έλαβε μήνυμα:<<"Hello again">2> server2:start(). έναρξη διακομιστή στη θύρα 1234 ok Υποδοχή #1 αναμονή για πελάτη Υποδοχή #2 αναμονή για πελάτη Υποδοχή #3 αναμονή για πελάτη Υποδοχή #4 αναμονή για πελάτη Υποδοχή #5 αναμονή για πελάτη Υποδοχή #1, έναρξη περιόδου λειτουργίας Η υποδοχή #1 έλαβε μήνυμα:

> Υποδοχή #1, περίοδος λειτουργίας κλειστή Υποδοχή #1 αναμονή για πελάτη gen_tcpΌλα αυτά είναι καλά και καλά, αλλά πραγματικά δεν χρειάζεται να ασχοληθείτε με μη αυτόματο τρόπο με το πακέτο κεφαλίδων. Αυτό έχει ήδη εφαρμοστεί σε

. Πρέπει να καθορίσετε το μέγεθος του πακέτου υπηρεσιών στις ρυθμίσεις όταν ανοίγετε μια υποδοχή στην πλευρά του πελάτη:

(ok, Socket) = gen_tcp:connect(Host, Port, ),

και από την πλευρά του διακομιστή:

(ok, ListenSocket) = gen_tcp:listen(Port, ),

και η ανάγκη να σχηματίσετε και να αναλύσετε μόνοι σας αυτές τις κεφαλίδες εξαφανίζεται.

Από την πλευρά του πελάτη, η αποστολή απλοποιείται:

Gen_tcp:send(Socket, Msg),

και από την πλευρά του διακομιστή διευκολύνει τη λήψη:

Handle_connection(Id, ListenSocket, Socket) -> case gen_tcp:recv(Socket, 0) of (ok, Msg) -> io:format("Socket #~p πήρε μήνυμα: ~p~n", ), gen_tcp:send (Socket, Msg), handle_connection(Id, ListenSocket, Socket); (σφάλμα, κλειστό) -> io:format("Socket #~p, session κλειστή ~n", ), accept(Id, ListenSocket) end.Τώρα όταν καλείτε gen_tcp gen_tcp:recv/2

καθορίζουμε Μήκος = 0.

Ξέρει πόσα byte πρέπει να διαβαστούν από την υποδοχή.

Εργασία με πρωτόκολλα κειμένου

Εκτός από την επιλογή κεφαλίδας υπηρεσίας, υπάρχει μια άλλη προσέγγιση. Μπορείτε να διαβάσετε από την υποδοχή ένα byte τη φορά μέχρι να συναντήσετε ένα ειδικό byte, που συμβολίζει το τέλος του πακέτου. Αυτό μπορεί να είναι ένα μηδενικό byte ή ένας χαρακτήρας νέας γραμμής. gen_tcpΑυτή η επιλογή είναι τυπική για πρωτόκολλα κειμένου (SMTP, POP3, FTP). Δεν χρειάζεται να γράψετε τη δική σας εφαρμογή ανάγνωσης από πρίζα, όλα έχουν ήδη εφαρμοστεί. Απλώς πρέπει να καθορίσετε στις ρυθμίσεις υποδοχής (πακέτο, 2).

και από την πλευρά του διακομιστή:

επιλογή

$ telnet localhost 1234 Προσπάθεια 127.0.0.1... Συνδέθηκε με localhost. Ο χαρακτήρας διαφυγής είναι "^]". γεια γεια γεια γεια και πάλι γεια και πάλι ^] telnet> έξοδος Η σύνδεση έκλεισε.

Θα χρειαστούμε έναν διακομιστή TCP, ένα πρωτόκολλο κειμένου και έναν πελάτη telnet στην εργασία μας.

Εξ ου και η «όξυνση» αυτού του πρωτοκόλλου για την εργασία με μεμονωμένα έγγραφα, κυρίως με κείμενο. Το HTTP χρησιμοποιεί τις δυνατότητες του TCP/IP στη δουλειά του, οπότε ας δούμε τις δυνατότητες που παρέχει η java για την εργασία με το τελευταίο.

Στην Java, υπάρχει ένα ειδικό πακέτο "java.net" για αυτό, που περιέχει την κλάση java.net.Socket. Socket στη μετάφραση σημαίνει "πρίζα" αυτό το όνομα δόθηκε κατ' αναλογία με τις πρίζες στον εξοπλισμό, αυτές ακριβώς όπου συνδέονται τα βύσματα. Σύμφωνα με αυτήν την αναλογία, μπορείτε να συνδέσετε δύο "πρίζες" και να μεταφέρετε δεδομένα μεταξύ τους. Κάθε φωλιά ανήκει σε συγκεκριμένο ξενιστή (Host - ιδιοκτήτης, κάτοχος). Κάθε κεντρικός υπολογιστής έχει μια μοναδική διεύθυνση IP (Πακέτο Διαδικτύου). Επί αυτή τη στιγμήΤο Διαδίκτυο λειτουργεί χρησιμοποιώντας το πρωτόκολλο IPv4, όπου η διεύθυνση IP είναι γραμμένη σε 4 αριθμούς από το 0 έως το 255 - για παράδειγμα, 127.0.0.1 (διαβάστε περισσότερα σχετικά με τη διανομή των διευθύνσεων IP εδώ - RFC 790, RFC 1918, RFC 2365, διαβάστε σχετικά η έκδοση IPv6 εδώ - RFC 2373)

Οι υποδοχές είναι τοποθετημένες στη θύρα υποδοχής (θύρα). Μια θύρα ορίζεται από έναν αριθμό από το 0 έως το 65535 και λογικά υποδεικνύει ένα μέρος όπου μπορεί να συνδεθεί μια υποδοχή. Εάν μια θύρα σε αυτόν τον κεντρικό υπολογιστή είναι ήδη κατειλημμένη από κάποια υποδοχή, τότε δεν θα είναι πλέον δυνατή η σύνδεση άλλης υποδοχής εκεί. Έτσι, αφού εγκατασταθεί η υποδοχή, έχει μια πολύ συγκεκριμένη διεύθυνση, συμβολικά γραμμένη ως εξής: για παράδειγμα - 127.0.0.1:8888 (σημαίνει ότι η υποδοχή καταλαμβάνει τη θύρα 8888 στον κεντρικό υπολογιστή 127.0.0.1)

Για να γίνει η ζωή ευκολότερη, ώστε να μην χρησιμοποιηθεί μια άβολη διεύθυνση IP, εφευρέθηκε το σύστημα DNS (DNS - Όνομα ΤομέαΥπηρεσία). Ο σκοπός αυτού του συστήματος είναι να αντιστοιχίσει συμβολικά ονόματα σε διευθύνσεις IP. Για παράδειγμα, η διεύθυνση "127.0.0.1" στους περισσότερους υπολογιστές σχετίζεται με το όνομα "localhost" (στην κοινή γλώσσα - "localhost").

Localhost, στην πραγματικότητα, σημαίνει τον ίδιο τον υπολογιστή στον οποίο εκτελείται το πρόγραμμα, είναι επίσης ο τοπικός υπολογιστής. Όλες οι εργασίες με το localhost δεν απαιτούν πρόσβαση στο δίκτυο και επικοινωνία με άλλους κεντρικούς υπολογιστές.

Υποδοχή πελάτη

Λοιπόν, ας επιστρέψουμε στην κλάση java.net.Socket Είναι πιο βολικό να το αρχικοποιήσουμε ως εξής:

Το Public Socket (κεντρικός υπολογιστής συμβολοσειράς, θύρα int) ρίχνει UnknownHostException, IOException Στον κεντρικό υπολογιστή σταθερής συμβολοσειράς, μπορείτε να καθορίσετε τόσο τη διεύθυνση IP του διακομιστή όσο και του Όνομα DNS. Σε αυτήν την περίπτωση, το πρόγραμμα θα επιλέξει αυτόματα μια ελεύθερη θύρα τοπικός υπολογιστήςκαι "βιδώστε" την υποδοχή σας εκεί, μετά από την οποία θα γίνει προσπάθεια να επικοινωνήσετε με μια άλλη πρίζα, η διεύθυνση της οποίας καθορίζεται στις παραμέτρους αρχικοποίησης. Σε αυτήν την περίπτωση, ενδέχεται να προκύψουν δύο τύποι εξαιρέσεων: άγνωστη διεύθυνση κεντρικού υπολογιστή - όταν δεν υπάρχει υπολογιστής με το ίδιο όνομα στο δίκτυο ή σφάλμα ότι δεν υπάρχει σύνδεση με αυτήν την υποδοχή.

Είναι επίσης χρήσιμο να γνωρίζετε τη λειτουργία

Δημόσιο κενό setSoTimeout(int timeout) ρίχνει SocketException Αυτή η συνάρτηση ορίζει το χρονικό όριο για την εργασία με μια υποδοχή. Εάν σε αυτό το διάστημα δεν γίνουν ενέργειες με την πρίζα (δηλαδή λήψη και αποστολή δεδομένων), τότε αυτοκαταστρέφεται. Ο χρόνος ρυθμίζεται σε δευτερόλεπτα, όταν το χρονικό όριο έχει οριστεί στο 0, η υποδοχή γίνεται "αιώνια".

Για ορισμένα δίκτυα, η αλλαγή του χρονικού ορίου δεν είναι δυνατή ή ρυθμίζεται σε συγκεκριμένα διαστήματα (για παράδειγμα, από 20 έως 100 δευτερόλεπτα). Εάν προσπαθήσετε να ορίσετε ένα μη έγκυρο χρονικό όριο, θα δημιουργηθεί μια κατάλληλη εξαίρεση.

Το πρόγραμμα που ανοίγει αυτόν τον τύπο υποδοχής θα ονομάζεται πελάτης και το πρόγραμμα στο οποίο ανήκει η υποδοχή στην οποία προσπαθείτε να συνδεθείτε θα ονομάζεται διακομιστής. Στην πραγματικότητα, κατ' αναλογία με μια πρίζα, το πρόγραμμα διακομιστή θα είναι η πρίζα και ο πελάτης είναι ακριβώς το βύσμα.

Υποδοχή διακομιστή

Μόλις περιέγραψα πώς να δημιουργήσετε μια σύνδεση από έναν πελάτη σε έναν διακομιστή, τώρα πώς να δημιουργήσετε μια υποδοχή που θα εξυπηρετεί τον διακομιστή. Για το σκοπό αυτό, υπάρχει η ακόλουθη κλάση στην Java: java.net.ServerSocket Το πιο βολικό πρόγραμμα προετοιμασίας για αυτό είναι το εξής:

Το Public ServerSocket(int port, int backlog, InetAddress bindAddr) ρίχνει το IOException Όπως μπορείτε να δείτε, ένα αντικείμενο μιας άλλης κλάσης χρησιμοποιείται ως τρίτη παράμετρος - java.net.InetAddress Αυτή η κλάση παρέχει εργασία με ονόματα DNS και IP, επομένως ο παραπάνω αρχικοποιητής μπορεί να χρησιμοποιηθεί σε προγράμματα όπως αυτό: ServerSocket(port, 0, InetAddress.getByName(host)) ρίχνει IOException Για αυτόν τον τύπο υποδοχής, η θύρα εγκατάστασης καθορίζεται απευθείας, επομένως, κατά την προετοιμασία, μπορεί να προκύψει μια εξαίρεση που υποδεικνύει ότι αυτό το λιμάνιχρησιμοποιείται ήδη ή απαγορεύεται η χρήση από την πολιτική ασφαλείας του υπολογιστή.

Μετά την εγκατάσταση της πρίζας, καλείται η λειτουργία

Η Public Socket δέχεται() ρίχνει IOException Αυτή η συνάρτηση αναγκάζει το πρόγραμμα να περιμένει να συνδεθεί ο πελάτης στην υποδοχή διακομιστή. Μόλις δημιουργηθεί η σύνδεση, η συνάρτηση θα επιστρέψει ένα αντικείμενο κλάσης Socket για επικοινωνία με τον πελάτη.

Client-server μέσω υποδοχών. Παράδειγμα

Για παράδειγμα - απλούστερο πρόγραμμα, που υλοποιεί εργασία με πρίζες.

Από την πλευρά του πελάτη, το πρόγραμμα λειτουργεί ως εξής: ο πελάτης συνδέεται με τον διακομιστή, στέλνει δεδομένα, στη συνέχεια λαμβάνει δεδομένα από τον διακομιστή και τα εξάγει.

Από την πλευρά του διακομιστή, μοιάζει με αυτό: ο διακομιστής θέτει την υποδοχή διακομιστή στη θύρα 3128 και στη συνέχεια περιμένει για εισερχόμενες συνδέσεις. Έχοντας αποδεχτεί μια νέα σύνδεση, ο διακομιστής τη μεταφέρει σε ξεχωριστό νήμα υπολογιστών. Στη νέα ροή, ο διακομιστής λαμβάνει δεδομένα από τον πελάτη και τα εκχωρεί σε αυτόν αύξων αριθμόςσυνδέσεις και στέλνει δεδομένα πίσω στον πελάτη.


Λογική δομή των παραδειγμάτων προγραμμάτων

Απλό πρόγραμμα πελάτη TCP/IP

(SampleClient.java) εισαγωγή java. ιο.* ; εισαγωγή java. καθαρό.* ; κλάση SampleClient επεκτείνει το νήμα ( public static void main(String args) ( try (// ανοίξτε την υποδοχή και συνδεθείτε στο localhost:3128 // λάβετε την υποδοχή διακομιστή Socket s = new Socket("localhost" , 3128);// Πάρτε τη ροή εξόδου και εξάγετε το πρώτο όρισμα εκεί // καθορίζεται κατά τη διάρκεια της κλήσης, η διεύθυνση της ανοιχτής υποδοχής και η θύρα της args[ 0 ] = args[ 0 ] + "\n" + s. getInetAddress() . getHostAddress() + ":" + s. getLocalPort(); μικρό. getOutputStream() . write(args[ 0 ] . getBytes()); } }

// διαβάστε την απάντηση byte buf = νέο byte [ 64 * 1024 ];

int r = s. getInputStream() . read(buf); Δεδομένα συμβολοσειράς = νέα συμβολοσειρά (buf, 0 , r); // εξάγει την απόκριση στην κονσόλαΣύστημα. έξω. println(data); ) catch (Εξαίρεση e) ( System. out. println("init error: " + e);) // εξαιρέσεις εξόδουΑπλό πρόγραμμα διακομιστή TCP/IP μικρό. getOutputStream() . write(args[ 0 ] . getBytes());(SampleServer.java) εισαγωγή java. ιο.* ; εισαγωγή java. καθαρό.* ;η κλάση SampleServer επεκτείνει το νήμα ( Υποδοχή s; int num; δημόσιο στατικό κενό main (String args) (δοκιμάστε ( int i = 0 ; // μετρητής σύνδεσης // βιδώστε την υποδοχή στο localhost, θύρα 3128()) Διακομιστής ServerSocket = νέος ServerSocket(3128, 0, InetAddress. getByName("localhost" )); Σύστημα. έξω. println("ο διακομιστής ξεκίνησε" );Η ροή εισόδου είναι = s. getInputStream(); // και από εκεί - ροή δεδομένων από διακομιστή σε πελάτη OutputStream os = s. getOutputStream(); // buffer δεδομένων 64 kilobyte byte buf = νέο byte [ 64 * 1024 ]; // ανάγνωση 64 kb από τον πελάτη, το αποτέλεσμα είναι ο αριθμός των δεδομένων που ελήφθησαν πραγματικά int r = είναι. read(buf); // δημιουργήστε μια συμβολοσειρά που περιέχει τις πληροφορίες που λαμβάνονται από τον πελάτηΔεδομένα συμβολοσειράς = νέα συμβολοσειρά (buf, 0 , r); // προσθήκη δεδομένων σχετικά με τη διεύθυνση υποδοχής: data = "" + num+ ": " + "\n" + δεδομένα; // δεδομένα εξόδου: os. write(data. getBytes()); // τερματίστε τη σύνδεσημικρό. κοντά(); ) catch (Εξαίρεση e) ( System. out. println("init error: " + e);) μικρό. getOutputStream() . write(args[ 0 ] . getBytes()); } }

Μετά τη μεταγλώττιση, παίρνουμε τα αρχεία SampleServer.class και SampleClient.class (όλα τα προγράμματα εδώ και παρακάτω μεταγλωττίζονται χρησιμοποιώντας JDK v1.4) και ξεκινάμε πρώτα τον διακομιστή:

Java SampleServer και, στη συνέχεια, μετά την αναμονή για το μήνυμα "ο διακομιστής έχει ξεκινήσει" και οποιονδήποτε αριθμό πελατών: java SampleClient test1 java SampleClient test2 ... java SampleClient testN

Εάν, κατά την εκκίνηση του προγράμματος διακομιστή, αντί για τη γραμμή "ο διακομιστής εκκινήθηκε", παρήγαγε μια γραμμή όπως

Σφάλμα έναρξης: java.net.BindException: Διεύθυνση που χρησιμοποιείται ήδη: JVM_Bind, τότε αυτό θα σημαίνει ότι η θύρα 3128 στον υπολογιστή σας είναι ήδη κατειλημμένη από κάποιο πρόγραμμα ή απαγορεύεται η χρήση από την πολιτική ασφαλείας.

Σημειώσεις

Ας σημειώσουμε ένα σημαντικό χαρακτηριστικό της υποδοχής διακομιστή: μπορεί να δέχεται συνδέσεις από πολλούς πελάτες ταυτόχρονα. Θεωρητικά, ο αριθμός των ταυτόχρονων συνδέσεων είναι απεριόριστος, αλλά σχεδόν τα πάντα εξαρτώνται από την ισχύ των υπολογιστών. Παρεμπιπτόντως, αυτό το πρόβλημα της πεπερασμένης ισχύος των υπολογιστών χρησιμοποιείται σε επιθέσεις DOS σε διακομιστές: απλώς βομβαρδίζονται με τόσες πολλές συνδέσεις που οι υπολογιστές δεν μπορούν να αντεπεξέλθουν στο φορτίο και να "κολλήσουν".

Σε αυτήν την περίπτωση, χρησιμοποιώντας το παράδειγμα του SimpleServer, δείχνω πώς να επεξεργάζεστε πολλές ταυτόχρονες συνδέσεις ταυτόχρονα: η υποδοχή κάθε νέας σύνδεσης αποστέλλεται σε ξεχωριστό νήμα υπολογιστών για επεξεργασία.

Αξίζει να σημειωθεί ότι η αφαίρεση Socket - ServerSocket και η εργασία με ροές δεδομένων χρησιμοποιούνται από C/C++, Perl, Python και πολλές άλλες γλώσσες προγραμματισμού και API λειτουργικών συστημάτων, οπότε πολλά από αυτά που ειπώθηκαν ισχύουν όχι μόνο για την πλατφόρμα Java.

Υποδοχές

Υποδοχήείναι το ένα άκρο ενός καναλιού αμφίδρομης επικοινωνίας μεταξύ δύο προγραμμάτων που εκτελούνται στο δίκτυο. Συνδέοντας δύο υποδοχές μαζί, μπορείτε να μεταφέρετε δεδομένα μεταξύ διαφορετικών διεργασιών (τοπικές ή απομακρυσμένες). Η υλοποίηση του socket παρέχει ενθυλάκωση των πρωτοκόλλων του επιπέδου δικτύου και μεταφοράς.

Οι υποδοχές αναπτύχθηκαν αρχικά για το UNIX στο Πανεπιστήμιο της Καλιφόρνια στο Μπέρκλεϋ. Στο UNIX, η μέθοδος I/O επικοινωνίας ακολουθεί τον αλγόριθμο open/read/write/close. Για να μπορέσει να χρησιμοποιηθεί ένας πόρος, πρέπει να ανοίξει με τα κατάλληλα δικαιώματα και άλλες ρυθμίσεις. Μόλις ανοίξει ένας πόρος, τα δεδομένα μπορούν να διαβαστούν από ή να εγγραφούν σε. Μετά τη χρήση ενός πόρου, ο χρήστης πρέπει να καλέσει τη μέθοδο Close() για να ειδοποιήσει το λειτουργικό σύστημα ότι έχει τελειώσει με τον πόρο.

Πότε προστέθηκαν χαρακτηριστικά στο λειτουργικό σύστημα UNIX; Διαδικασία επικοινωνίας (IPC)και την ανταλλαγή δικτύου, δανείστηκε το γνωστό μοτίβο εισόδου-εξόδου. Όλοι οι πόροι που εκτίθενται για επικοινωνία στο UNIX και τα Windows αναγνωρίζονται από λαβές. Αυτοί οι περιγραφείς, ή λαβές, μπορεί να δείχνει σε ένα αρχείο, μνήμη ή κάποιο άλλο κανάλι επικοινωνίας, αλλά στην πραγματικότητα να δείχνει την εσωτερική δομή δεδομένων που χρησιμοποιείται λειτουργικό σύστημα. Η υποδοχή, που είναι ο ίδιος πόρος, αντιπροσωπεύεται επίσης από έναν περιγραφέα. Επομένως, για τις πρίζες, η διάρκεια ζωής μιας λαβής μπορεί να χωριστεί σε τρεις φάσεις: άνοιγμα (δημιουργία) της πρίζας, λήψη από ή αποστολή στην πρίζα και τέλος κλείσιμο της πρίζας.

Η διεπαφή IPC για την επικοινωνία μεταξύ διαφορετικών διεργασιών είναι χτισμένη πάνω σε μεθόδους I/O. Διευκολύνουν τις υποδοχές να στέλνουν και να λαμβάνουν δεδομένα. Κάθε στόχος καθορίζεται από μια διεύθυνση υποδοχής, επομένως αυτή η διεύθυνση μπορεί να καθοριστεί στον πελάτη για να δημιουργήσει μια σύνδεση με τον στόχο.

Τύποι υποδοχών

Υπάρχουν δύο κύριοι τύποι υποδοχών - οι υποδοχές ροής και οι υποδοχές datagram.

Υποδοχές ροής

Μια υποδοχή ρεύματος είναι μια πρίζα με εδραιωμένη σύνδεση, που αποτελείται από μια ροή byte που μπορεί να είναι αμφίδρομη, που σημαίνει ότι μια εφαρμογή μπορεί να στέλνει και να λαμβάνει δεδομένα μέσω αυτού του τελικού σημείου.

Μια υποδοχή ροής εξασφαλίζει διόρθωση σφαλμάτων, χειρίζεται την παράδοση και διατηρεί τη συνέπεια των δεδομένων. Μπορείτε να βασιστείτε σε αυτό για την παράδοση τακτοποιημένων, διπλών δεδομένων. Η υποδοχή ροής είναι επίσης κατάλληλη για μετάδοση μεγάλους όγκουςδεδομένων, δεδομένου ότι τα γενικά έξοδα που σχετίζονται με τη δημιουργία ξεχωριστής σύνδεσης για κάθε μήνυμα που αποστέλλεται μπορεί να είναι απαγορευτικά για μικρές ποσότητες δεδομένων. Οι υποδοχές ροής επιτυγχάνουν αυτό το επίπεδο ποιότητας χρησιμοποιώντας ένα πρωτόκολλο Πρωτόκολλο Ελέγχου Μετάδοσης (TCP). Το TCP διασφαλίζει ότι τα δεδομένα φτάνουν στην άλλη πλευρά με τη σωστή σειρά και χωρίς σφάλματα.

Για αυτόν τον τύπο υποδοχής, η διαδρομή σχηματίζεται πριν από την αποστολή μηνυμάτων. Αυτό διασφαλίζει ότι και τα δύο μέρη που συμμετέχουν στην αλληλεπίδραση αποδέχονται και ανταποκρίνονται. Εάν μια εφαρμογή στείλει δύο μηνύματα σε έναν παραλήπτη, είναι εγγυημένο ότι τα μηνύματα θα ληφθούν με την ίδια σειρά.

Ωστόσο, μεμονωμένα μηνύματα μπορεί να χωριστούν σε πακέτα και δεν υπάρχει τρόπος να καθοριστούν τα όρια των εγγραφών. Κατά τη χρήση του TCP, αυτό το πρωτόκολλο φροντίζει να σπάσει τα μεταδιδόμενα δεδομένα σε πακέτα του κατάλληλου μεγέθους, να τα στείλει στο δίκτυο και να τα επανασυναρμολογήσει στην άλλη πλευρά. Η εφαρμογή γνωρίζει μόνο τι στέλνει στο επίπεδο TCP συγκεκριμένο αριθμό bytes και η άλλη πλευρά λαμβάνει αυτά τα byte. Με τη σειρά του, το TCP διασπά αποτελεσματικά αυτά τα δεδομένα σε πακέτα κατάλληλο μέγεθος, λαμβάνει αυτά τα πακέτα από την άλλη πλευρά, εξάγει τα δεδομένα από αυτά και τα συνδυάζει μεταξύ τους.

Οι ροές βασίζονται σε ρητές συνδέσεις: η υποδοχή Α ζητά σύνδεση με την υποδοχή Β και η υποδοχή Β είτε αποδέχεται είτε απορρίπτει την αίτηση σύνδεσης.

Εάν τα δεδομένα πρέπει να είναι εγγυημένα ότι θα παραδοθούν στην άλλη πλευρά ή το μέγεθος των δεδομένων είναι μεγάλο, οι υποδοχές ροής είναι προτιμότερες από τις υποδοχές datagram. Επομένως, εάν η αξιόπιστη επικοινωνία μεταξύ δύο εφαρμογών είναι υψίστης σημασίας, επιλέξτε υποδοχές ροής.

Υπηρέτης e-mailπαρουσιάζει ένα παράδειγμα εφαρμογής που πρέπει να παραδώσει περιεχόμενο με τη σωστή σειρά, χωρίς επικαλύψεις ή παραλείψεις. Η υποδοχή ροής βασίζεται στο TCP για να διασφαλίσει ότι τα μηνύματα παραδίδονται στους προορισμούς τους.

Υποδοχές Datagram

Οι υποδοχές Datagram ονομάζονται μερικές φορές υποδοχές χωρίς σύνδεση, δηλαδή, δεν δημιουργείται ρητή σύνδεση μεταξύ τους - το μήνυμα αποστέλλεται στην καθορισμένη υποδοχή και, κατά συνέπεια, μπορεί να ληφθεί από την καθορισμένη υποδοχή.

Σε σύγκριση με τις υποδοχές datagram, οι υποδοχές ροής παρέχουν στην πραγματικότητα περισσότερα αξιόπιστη μέθοδος, αλλά για ορισμένες εφαρμογές τα γενικά έξοδα που σχετίζονται με τη δημιουργία μιας ρητής σύνδεσης είναι απαράδεκτα (για παράδειγμα, ένας διακομιστής ώρας της ημέρας που παρέχει συγχρονισμό ώρας στους πελάτες του). Εξάλλου, η δημιουργία αξιόπιστης σύνδεσης με τον διακομιστή απαιτεί χρόνο, κάτι που απλώς εισάγει καθυστερήσεις στην υπηρεσία και η εργασία της εφαρμογής διακομιστή αποτυγχάνει. Για να μειώσετε την επιβάρυνση, θα πρέπει να χρησιμοποιήσετε υποδοχές datagram.

Η χρήση υποδοχών datagram απαιτεί τη διαχείριση της μεταφοράς δεδομένων από τον πελάτη στον διακομιστή Πρωτόκολλο User Datagram (UDP). Σε αυτό το πρωτόκολλο, επιβάλλονται ορισμένοι περιορισμοί στο μέγεθος των μηνυμάτων και, σε αντίθεση με τις υποδοχές ροής, οι οποίες μπορούν να στείλουν αξιόπιστα μηνύματα στον διακομιστή προορισμού, οι υποδοχές datagram δεν παρέχουν αξιοπιστία. Εάν τα δεδομένα χαθούν κάπου στο δίκτυο, ο διακομιστής δεν θα αναφέρει σφάλματα.

Εκτός από τους δύο τύπους που συζητήθηκαν, υπάρχει επίσης μια γενικευμένη μορφή υποδοχών, η οποία ονομάζεται μη επεξεργασμένη ή ακατέργαστη.

Ακατέργαστες πρίζες

Ο κύριος σκοπός της χρήσης ακατέργαστων υποδοχών είναι να παρακάμψει τον μηχανισμό με τον οποίο ο υπολογιστής χειρίζεται το TCP/IP. Αυτό επιτυγχάνεται με την παροχή μιας ειδικής υλοποίησης της στοίβας TCP/IP που παρακάμπτει τον μηχανισμό που παρέχεται από τη στοίβα TCP/IP στον πυρήνα - το πακέτο μεταβιβάζεται απευθείας στην εφαρμογή και επομένως επεξεργάζεται πολύ πιο αποτελεσματικά από ό,τι κατά τη διέλευση από τη στοίβα του πελάτη κύρια στοίβα πρωτοκόλλου.

Εξ ορισμού, μια raw socket είναι μια υποδοχή που λαμβάνει πακέτα, παρακάμπτει τα επίπεδα TCP και UDP στη στοίβα TCP/IP και τα στέλνει απευθείας στην εφαρμογή.

Όταν χρησιμοποιείτε τέτοιες υποδοχές, το πακέτο δεν διέρχεται από το φίλτρο TCP/IP, δηλ. δεν υποβάλλεται σε επεξεργασία με κανέναν τρόπο και εμφανίζεται στην ακατέργαστη μορφή του. Σε αυτήν την περίπτωση, είναι ευθύνη της εφαρμογής λήψης να επεξεργαστεί σωστά όλα τα δεδομένα και να εκτελέσει ενέργειες όπως η αφαίρεση κεφαλίδων και η ανάλυση πεδίων - όπως η συμπερίληψη μιας μικρής στοίβας TCP/IP στην εφαρμογή.

Ωστόσο, δεν είναι συχνά ότι μπορεί να χρειαστείτε ένα πρόγραμμα που ασχολείται με ακατέργαστες πρίζες. Εάν δεν γράφετε ένα σύστημα λογισμικόή ένα πρόγραμμα παρόμοιο με έναν αναλυτή πακέτων, δεν θα χρειαστεί να μπείτε σε τέτοιες λεπτομέρειες. Οι Raw sockets χρησιμοποιούνται κυρίως για την ανάπτυξη εξειδικευμένων εφαρμογών πρωτοκόλλου χαμηλού επιπέδου. Για παράδειγμα, διάφορα βοηθητικά προγράμματα TCP/IP, όπως ίχνος διαδρομής, ping ή arp χρησιμοποιούν raw sockets.

Η εργασία με ακατέργαστες πρίζες απαιτεί ισχυρή γνώση βασικά πρωτόκολλα TCP/UDP/IP.

λιμάνια

Η θύρα έχει οριστεί για να επιτρέπει το πρόβλημα της ταυτόχρονης αλληλεπίδρασης με πολλαπλές εφαρμογές. Ουσιαστικά, επεκτείνει την έννοια της διεύθυνσης IP. Ένας υπολογιστής που εκτελεί πολλές εφαρμογές ταυτόχρονα και λαμβάνει ένα πακέτο από το δίκτυο μπορεί να αναγνωρίσει τη διαδικασία προορισμού χρησιμοποιώντας τον μοναδικό αριθμό θύρας που καθορίστηκε όταν πραγματοποιήθηκε η σύνδεση.

Η υποδοχή αποτελείται από τη διεύθυνση IP του μηχανήματος και τον αριθμό θύρας που χρησιμοποιείται από την εφαρμογή TCP. Επειδή μια διεύθυνση IP είναι μοναδική στο Διαδίκτυο και οι αριθμοί θύρας είναι μοναδικοί σε ένα μεμονωμένο μηχάνημα, οι αριθμοί υποδοχής είναι επίσης μοναδικοί σε ολόκληρο το Διαδίκτυο. Αυτό το χαρακτηριστικό επιτρέπει σε μια διεργασία να επικοινωνεί μέσω του δικτύου με μια άλλη διεργασία που βασίζεται αποκλειστικά στον αριθμό υποδοχής.

Οι αριθμοί θύρας δεσμεύονται για ορισμένες υπηρεσίες - αυτοί είναι γνωστοί αριθμοί θύρας, όπως η θύρα 21, που χρησιμοποιούνται στο FTP. Η εφαρμογή σας μπορεί να χρησιμοποιήσει οποιονδήποτε αριθμό θύρας που δεν έχει δεσμευτεί και δεν χρησιμοποιείται ακόμη. Πρακτορείο Αρχή Εκχωρημένων Αριθμών Διαδικτύου (IANA)διατηρεί μια λίστα με κοινά γνωστούς αριθμούς θύρας.

Συνήθως μια εφαρμογή πελάτη-διακομιστή που χρησιμοποιεί υποδοχές αποτελείται από δύο διαφορετικές εφαρμογές- ένας πελάτης που ξεκινά μια σύνδεση με έναν στόχο (διακομιστής) και ένας διακομιστής που περιμένει μια σύνδεση από τον πελάτη.

Για παράδειγμα, από την πλευρά του πελάτη, η εφαρμογή πρέπει να γνωρίζει τη διεύθυνση προορισμού και τον αριθμό θύρας. Με την αποστολή ενός αιτήματος σύνδεσης, ο πελάτης προσπαθεί να δημιουργήσει μια σύνδεση με τον διακομιστή:

Εάν τα συμβάντα εξελιχθούν με επιτυχία, υπό την προϋπόθεση ότι ο διακομιστής έχει ξεκινήσει πριν ο πελάτης επιχειρήσει να συνδεθεί σε αυτόν, ο διακομιστής συμφωνεί με τη σύνδεση. Έχοντας δώσει τη συγκατάθεσή της, η εφαρμογή διακομιστή δημιουργεί μια νέα υποδοχή για να αλληλεπιδρά συγκεκριμένα με τον πελάτη που δημιούργησε τη σύνδεση:

Τώρα ο πελάτης και ο διακομιστής μπορούν να αλληλεπιδρούν μεταξύ τους, διαβάζοντας μηνύματα ο καθένας από τη δική του υποδοχή και, κατά συνέπεια, γράφοντας μηνύματα.

Εργασία με υποδοχές στο .NET

Η υποστήριξη υποδοχής στο .NET παρέχεται από κλάσεις στο χώρο ονομάτων System.Net.Sockets- ας ξεκινήσουμε με τη σύντομη περιγραφή τους.

Μαθήματα για εργασία με πρίζες
Τάξη Περιγραφή
MulticastOption Η κλάση MulticastOption ορίζει την τιμή της διεύθυνσης IP για τη σύνδεση ή την έξοδο από μια ομάδα IP.
NetworkStream Η κλάση NetworkStream υλοποιεί τη βασική κλάση ροής από την οποία αποστέλλονται και λαμβάνονται δεδομένα. Αυτή είναι μια αφαίρεση υψηλού επιπέδου που αντιπροσωπεύει μια σύνδεση σε ένα κανάλι επικοινωνίας TCP/IP.
TcpClient Η κλάση TcpClient βασίζεται στην κλάση Socket για να παρέχει υπηρεσία TCP για περισσότερα υψηλό επίπεδο. Το TcpClient παρέχει διάφορες μεθόδους για την αποστολή και λήψη δεδομένων μέσω του δικτύου.
TcpListener Αυτή η κατηγορία βασίζεται επίσης στην κατηγορία Socket χαμηλού επιπέδου. Ο κύριος σκοπός του είναι εφαρμογές διακομιστή. Ακούει τα εισερχόμενα αιτήματα σύνδεσης από πελάτες και ειδοποιεί την εφαρμογή για τυχόν συνδέσεις.
UdpClient Το UDP είναι πρωτόκολλο χωρίς σύνδεση, επομένως απαιτείται διαφορετική λειτουργικότητα για την υλοποίηση της υπηρεσίας UDP στο .NET.
SocketException Αυτή η εξαίρεση εμφανίζεται όταν παρουσιαστεί σφάλμα στην πρίζα.
Υποδοχή Η τελευταία κλάση στον χώρο ονομάτων System.Net.Sockets είναι η ίδια η κλάση Socket. Παρέχει βασική λειτουργικότηταεφαρμογές υποδοχής.

Κατηγορία πρίζας

Η τάξη Socket παίζει σημαντικό ρόλο σε προγραμματισμός δικτύου, διασφαλίζοντας τη λειτουργία τόσο του πελάτη όσο και του διακομιστή. Κυρίως, οι κλήσεις προς μεθόδους αυτής της κλάσης εκτελούν τους απαραίτητους ελέγχους που σχετίζονται με την ασφάλεια, συμπεριλαμβανομένου του ελέγχου των δικαιωμάτων ασφαλείας, μετά τον οποίο προωθούνται στους αντίστοιχους τρόπους των μεθόδων στο API των Windows Sockets.

Πριν στραφούμε σε ένα παράδειγμα χρήσης της κλάσης Socket, ας δούμε μερικές σημαντικές ιδιότητες και μεθόδους αυτής της κλάσης:

Ιδιότητες και μέθοδοι της κλάσης Socket
Ιδιότητα ή μέθοδος Περιγραφή
Διεύθυνση Οικογένειας Δίνει στην οικογένεια διευθύνσεων υποδοχής - μια τιμή από την απαρίθμηση Socket.AddressFamily.
Διαθέσιμος Επιστρέφει τον όγκο των δεδομένων που είναι διαθέσιμα για ανάγνωση.
Αποκλεισμός Λαμβάνει ή ορίζει μια τιμή που υποδεικνύει εάν η πρίζα βρίσκεται σε λειτουργία αποκλεισμού.
Συνδεδεμένος Επιστρέφει μια τιμή που υποδεικνύει εάν η υποδοχή είναι συνδεδεμένη στον απομακρυσμένο κεντρικό υπολογιστή.
LocalEndPoint Δίνει το τοπικό τελικό σημείο.
Τύπος Πρωτοκόλλου Δίνει τον τύπο πρωτοκόλλου της υποδοχής.
RemoteEndPoint Δίνει το τελικό σημείο της απομακρυσμένης υποδοχής.
SocketType Δίνει τον τύπο πρίζας.
Αποδέχομαι() Δημιουργεί μια νέα υποδοχή για να χειριστεί ένα εισερχόμενο αίτημα σύνδεσης.
Δένω() Συνδέει μια πρίζα με μια τοπική τελικό σημείογια να ακούσετε για εισερχόμενα αιτήματα σύνδεσης.
Κοντά() Αναγκάζει την πρίζα να κλείσει.
Συνδέω() Δημιουργεί μια σύνδεση με έναν απομακρυσμένο κεντρικό υπολογιστή.
GetSocketOption() Επιστρέφει την τιμή SocketOption.
IOControl() Ρυθμίζει τους τρόπους λειτουργίας χαμηλού επιπέδου για την πρίζα. Αυτή η μέθοδος παρέχει πρόσβαση χαμηλού επιπέδου στην υποκείμενη κλάση Socket.
Ακούω() Θέτει την υποδοχή σε λειτουργία ακρόασης (αναμονής). Αυτή η μέθοδος είναι μόνο για εφαρμογές διακομιστή.
Λαμβάνω() Λαμβάνει δεδομένα από μια συνδεδεμένη πρίζα.
Ψηφοφορία() Καθορίζει την κατάσταση της πρίζας.
Επιλέγω() Ελέγχει την κατάσταση μιας ή περισσότερων υποδοχών.
Στέλνω() Στέλνει δεδομένα στη συνδεδεμένη πρίζα.
SetSocketOption() Ρυθμίζει την επιλογή υποδοχής.
Κλείσιμο() Απενεργοποιεί τις λειτουργίες αποστολής και λήψης στην υποδοχή.