java.io
πακέτο και NIO, το μη αποκλεισμένο I / O (java.nio
API που εισήχθησαν στην Java 1.4. Τέλος, θα δείτε ένα παράδειγμα που δείχνει τη δικτύωση Java όπως υλοποιείται από το Java 7 forward, στο NIO.2.Ο προγραμματισμός υποδοχών βασίζεται σε δύο συστήματα που επικοινωνούν μεταξύ τους. Γενικά, η επικοινωνία δικτύου διατίθεται σε δύο γεύσεις: Πρωτόκολλο Ελέγχου Μεταφορών (TCP) και Πρωτόκολλο Δεδομένου Χρήστη (UDP). Τα TCP και UDP χρησιμοποιούνται για διαφορετικούς σκοπούς και και τα δύο έχουν μοναδικούς περιορισμούς:
- Το TCP είναι σχετικά απλό και αξιόπιστο πρωτόκολλο που επιτρέπει στον πελάτη να κάνει σύνδεση με έναν διακομιστή και τα δύο συστήματα να επικοινωνούν. Στο TCP, κάθε οντότητα γνωρίζει ότι έχουν ληφθεί ωφέλιμα φορτία επικοινωνίας.
- Το UDP είναι ένα πρωτόκολλο χωρίς σύνδεση και είναι καλό για σενάρια όπου δεν χρειάζεστε απαραίτητα κάθε πακέτο για να φτάσετε στον προορισμό του, όπως η ροή πολυμέσων.
Για να εκτιμήσετε τη διαφορά μεταξύ TCP και UDP, σκεφτείτε τι θα συνέβαινε εάν μεταφέρατε βίντεο από τον αγαπημένο σας ιστότοπο και έπεσε καρέ. Θα προτιμούσατε ο πελάτης να επιβραδύνει την ταινία σας για να λάβει τα καρέ που λείπουν ή θα προτιμούσατε το βίντεο να συνεχίσει να παίζει; Τα πρωτόκολλα ροής βίντεο αξιοποιούν συνήθως UDP. Επειδή το TCP εγγυάται την παράδοση, είναι το πρωτόκολλο επιλογής για HTTP, FTP, SMTP, POP3 και ούτω καθεξής.
Σε αυτό το σεμινάριο, σας παρουσιάζω τον προγραμματισμό υποδοχής στην Java. Παρουσιάζω μια σειρά παραδειγμάτων διακομιστή-πελάτη που αποδεικνύουν χαρακτηριστικά από το αρχικό πλαίσιο Java I / O και μετά προχωρούν σταδιακά στη χρήση λειτουργιών που παρουσιάζονται στο NIO.2
Παλιές σχολές Java
Σε εφαρμογές πριν από το NIO, ο κώδικας υποδοχής πελάτη Java TCP αντιμετωπίζεται από το java.net.Socket
τάξη. Ο ακόλουθος κώδικας ανοίγει μια σύνδεση σε έναν διακομιστή:
Υποδοχή υποδοχής = νέα υποδοχή (διακομιστής, θύρα);
Μόλις μας πρίζα
Το παράδειγμα είναι συνδεδεμένο με τον διακομιστή, μπορούμε να αρχίσουμε να λαμβάνουμε ροές εισόδου και εξόδου στη διακοπή. Οι ροές εισόδου χρησιμοποιούνται για την ανάγνωση δεδομένων από το διακομιστή, ενώ οι ροές εξόδου χρησιμοποιούνται για την εγγραφή δεδομένων στο διακομιστή. Μπορούμε να εκτελέσουμε τις ακόλουθες μεθόδους για τη λήψη ροών εισόδου και εξόδου:
InputStream σε = socket.getInputStream (); OutputStream out = socket.getOutputStream ();
Επειδή αυτές είναι συνηθισμένες ροές, οι ίδιες ροές με τις οποίες θα χρησιμοποιούσαμε για ανάγνωση και εγγραφή σε ένα αρχείο, μπορούμε να τις μετατρέψουμε στη φόρμα που εξυπηρετεί καλύτερα τη θήκη χρήσης μας. Για παράδειγμα, θα μπορούσαμε να τυλίξουμε το Έξοδος ροής
με PrintStream
ώστε να μπορούμε να γράφουμε εύκολα κείμενο με μεθόδους όπως εκτύπωση ()
. Για ένα άλλο παράδειγμα, θα μπορούσαμε να τυλίξουμε το InputStream
με BufferedReader
, μέσω ενός InputStreamReader
, για να διαβάσετε εύκολα κείμενο με μεθόδους όπως readLine ()
.
Παράδειγμα πελάτη Java socket
Ας δούμε ένα σύντομο παράδειγμα που εκτελεί ένα HTTP GET έναντι ενός διακομιστή HTTP. Το HTTP είναι πιο εξελιγμένο από το παράδειγμά μας, αλλά μπορούμε να γράψουμε κώδικα πελάτη για να χειριστούμε την απλούστερη περίπτωση: ζητήστε έναν πόρο από τον διακομιστή και ο διακομιστής επιστρέφει την απόκριση και κλείνει τη ροή. Αυτή η περίπτωση απαιτεί τα ακόλουθα βήματα:
- Δημιουργήστε μια υποδοχή στον διακομιστή ιστού που ακούει στη θύρα 80.
- Αποκτήστε ένα
PrintStream
στον διακομιστή και στείλτε το αίτημαΛάβετε PATH HTTP / 1.0
, όπουΜΟΝΟΠΑΤΙ
είναι ο πόρος που ζητήθηκε στον διακομιστή. Για παράδειγμα, εάν θέλαμε να ανοίξουμε τη ρίζα ενός ιστότοπου, τότε η διαδρομή θα ήταν/
. - Αποκτήστε ένα
InputStream
στον διακομιστή, τυλίξτε το με έναBufferedReader
και διαβάστε την απόκριση γραμμή προς γραμμή.
Η λίστα 1 εμφανίζει τον πηγαίο κώδικα για αυτό το παράδειγμα.
Λίστα 1. SimpleSocketClientExample.java
πακέτο com.geekcap.javaworld.simplesocketclient; εισαγωγή java.io.BufferedReader; εισαγωγή java.io.InputStreamReader; εισαγωγή java.io.PrintStream; εισαγωγή java.net.Socket; δημόσια τάξη SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Usage: SimpleSocketClientExample"); System.exit (0); } Διακομιστής συμβολοσειράς = args [0]; String path = args [1]; System.out.println ("Φόρτωση περιεχομένων της διεύθυνσης URL:" + διακομιστής); δοκιμάστε {// Σύνδεση στο διακομιστή Socket socket = new Socket (server, 80). // Δημιουργήστε ροές εισόδου και εξόδου για ανάγνωση και εγγραφή στον διακομιστή PrintStream out = new PrintStream (socket.getOutputStream ()); BufferedReader σε = νέο BufferedReader (νέο InputStreamReader (socket.getInputStream ())); // Ακολουθήστε το πρωτόκολλο HTTP του GET HTTP / 1.0 ακολουθούμενο από μια κενή γραμμή out.println ("GET" + διαδρομή + "HTTP / 1.0"); out.println (); // Διαβάστε τα δεδομένα από το διακομιστή μέχρι να ολοκληρώσουμε την ανάγνωση του εγγράφου String line = in.readLine (); ενώ (γραμμή! = null) {System.out.println (γραμμή); line = in.readLine (); } // Κλείστε τις ροές μας στο.close (); out.close (); socket.close (); } catch (Εξαίρεση e) {e.printStackTrace (); }}}
Η λίστα 1 δέχεται δύο ορίσματα γραμμής εντολών: τον διακομιστή στον οποίο θα συνδεθεί (υποθέτοντας ότι συνδέουμε με τον διακομιστή στη θύρα 80) και τον πόρο για ανάκτηση. Δημιουργεί ένα Πρίζα
που οδηγεί στο διακομιστή και καθορίζει ρητά τη θύρα 80
. Στη συνέχεια εκτελεί την εντολή:
Λάβετε PATH HTTP / 1.0
Για παράδειγμα:
GET / HTTP / 1.0
Τι συνέβη μόλις τώρα?
Όταν ανακτάτε μια ιστοσελίδα από έναν διακομιστή ιστού, όπως www.google.com
, ο πελάτης HTTP χρησιμοποιεί διακομιστές DNS για να βρει τη διεύθυνση του διακομιστή: ξεκινά ζητώντας από το διακομιστή τομέα ανώτερου επιπέδου για το com
τομέα όπου ο εξουσιοδοτημένος διακομιστής ονόματος τομέα είναι για το www.google.com
. Στη συνέχεια, ζητά από αυτόν τον διακομιστή ονόματος τομέα για τη διεύθυνση IP (ή τις διευθύνσεις) για www.google.com
. Στη συνέχεια, ανοίγει μια υποδοχή σε αυτόν τον διακομιστή στη θύρα 80. (Ή, εάν θέλετε να ορίσετε μια διαφορετική θύρα, μπορείτε να το κάνετε προσθέτοντας μια άνω και κάτω τελεία ακολουθούμενη από τον αριθμό θύρας, για παράδειγμα: :8080
.) Τέλος, ο πελάτης HTTP εκτελεί την καθορισμένη μέθοδο HTTP, όπως ΠΑΙΡΝΩ
, ΘΕΣΗ
, ΒΑΖΩ
, ΔΙΑΓΡΑΦΩ
, ΚΕΦΑΛΙ
, ή OPTI / ONS
. Κάθε μέθοδος έχει τη δική της σύνταξη. Όπως φαίνεται στα παραπάνω αποσπάσματα κώδικα, το ΠΑΙΡΝΩ
μέθοδος απαιτεί μια διαδρομή που ακολουθείται από Αριθμός HTTP / έκδοσης
και μια κενή γραμμή. Αν θέλαμε να προσθέσουμε κεφαλίδες HTTP, θα μπορούσαμε να το κάνουμε πριν εισέλθουμε στη νέα γραμμή.
Στην καταχώριση 1, ανακτήσαμε ένα Έξοδος ροής
και το τυλίχτηκε σε ένα PrintStream
ώστε να μπορούμε να εκτελούμε ευκολότερα τις εντολές που βασίζονται σε κείμενο. Ο κωδικός μας έλαβε ένα InputStream
, τυλιγμένο σε ένα InputStreamReader
, το οποίο μετέτρεψε σε α Αναγνώστης
, και μετά το τυλίχτηκε σε ένα BufferedReader
. Χρησιμοποιήσαμε το PrintStream
για να εκτελέσουμε το δικό μας ΠΑΙΡΝΩ
μέθοδο και στη συνέχεια χρησιμοποίησε το BufferedReader
για να διαβάσετε την απόκριση γραμμή προς γραμμή μέχρι να λάβουμε ένα μηδενικό
απάντηση, υποδεικνύοντας ότι η υποδοχή είχε κλείσει.
Τώρα εκτελέστε αυτήν την τάξη και περάστε τα παρακάτω ορίσματα:
java com.geekcap.javaworld.simplesocketclient.SimpleSocketClient Παράδειγμα www.javaworld.com /
Θα πρέπει να δείτε έξοδο παρόμοιο με αυτό που ακολουθεί:
Φόρτωση περιεχομένων της διεύθυνσης URL: www.javaworld.com HTTP / 1.1 200 OK Ημερομηνία: Κυρ, 21 Σεπ 2014 22:20:13 GMT Server: Apache X-Gas_TTL: 10 Cache-Control: max-age = 10 X-GasHost: gas2 .usw X-Cooking-With: Βενζίνη-Τοπικό X-Βενζίνη-Ηλικία: 8 Μήκος περιεχομένου: 168 Τελευταία τροποποίηση: Τρί, 24 Ιαν 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" Τύπος περιεχομένου : text / html Ποικιλία: Σύνδεση αποδοχής-κωδικοποίησης: κλείστε τη σελίδα δοκιμής βενζίνηςΕπιτυχία
Αυτή η έξοδος εμφανίζει μια δοκιμαστική σελίδα στον ιστότοπο του JavaWorld. Απάντησε πίσω ότι μιλάει HTTP έκδοση 1.1 και η απάντηση είναι 200 ΟΚ
.
Παράδειγμα διακομιστή υποδοχής Java
Καλύψαμε την πλευρά του πελάτη και ευτυχώς η πλευρά επικοινωνίας του διακομιστή είναι εξίσου εύκολη. Από μια απλοϊκή προοπτική, η διαδικασία έχει ως εξής:
- Δημιουργώ ένα
Υποδοχή διακομιστή
, καθορίζοντας μια θύρα για ακρόαση. - Επικαλέστε το
Υποδοχή διακομιστή
'μικρόαποδέχομαι()
μέθοδο για ακρόαση στη διαμορφωμένη θύρα για σύνδεση πελάτη. - Όταν ένας πελάτης συνδέεται με το διακομιστή, το
αποδέχομαι()
η μέθοδος επιστρέφει aΠρίζα
μέσω του οποίου ο διακομιστής μπορεί να επικοινωνήσει με τον πελάτη. Αυτό είναι το ίδιοΠρίζα
τάξη που χρησιμοποιήσαμε για τον πελάτη μας, οπότε η διαδικασία είναι η ίδια: αποκτήστε έναInputStream
για να διαβάσετε από τον πελάτη και έναΈξοδος ροής
γράψτε στον πελάτη. - Εάν ο διακομιστής σας πρέπει να είναι επεκτάσιμος, θα θελήσετε να περάσετε το
Πρίζα
σε άλλο νήμα για επεξεργασία, ώστε ο διακομιστής σας να μπορεί να συνεχίσει να ακούει για επιπλέον συνδέσεις. - Καλέστε το
Υποδοχή διακομιστή
'μικρόαποδέχομαι()
μέθοδος ξανά για να ακούσετε μια άλλη σύνδεση.
Όπως θα δείτε σύντομα, ο χειρισμός αυτού του σεναρίου από τον NIO θα ήταν λίγο διαφορετικός. Προς το παρόν, όμως, μπορούμε άμεσα να δημιουργήσουμε ένα Υποδοχή διακομιστή
περνώντας το ένα λιμάνι για να το ακούσετε (περισσότερα για ServerSocketFactory
s στην επόμενη ενότητα):
ServerSocket serverSocket = νέο ServerSocket (θύρα);
Και τώρα μπορούμε να δεχτούμε εισερχόμενες συνδέσεις μέσω του αποδέχομαι()
μέθοδος:
Socket socket = serverSocket.accept (); // Χειριστείτε τη σύνδεση ...
Πολυνηματικός προγραμματισμός με υποδοχές Java
Η καταχώριση 2, παρακάτω, τοποθετεί όλο τον κώδικα του διακομιστή μέχρι στιγμής σε ένα ελαφρώς πιο ισχυρό παράδειγμα που χρησιμοποιεί νήματα για τον χειρισμό πολλαπλών αιτημάτων. Ο διακομιστής που εμφανίζεται είναι ένας διακομιστής echo, που σημαίνει ότι επαναλαμβάνει οποιοδήποτε μήνυμα λαμβάνει.
Παρόλο που το παράδειγμα στη λίστα 2 δεν είναι περίπλοκο, προβλέπει μερικά από αυτά που θα εμφανιστούν στην επόμενη ενότητα στο NIO. Δώστε ιδιαίτερη προσοχή στο μέγεθος του κώδικα νήματος που πρέπει να γράψουμε για να δημιουργήσουμε έναν διακομιστή που μπορεί να χειριστεί πολλαπλά ταυτόχρονα αιτήματα.
Λίστα 2. SimpleSocketServer.java
πακέτο com.geekcap.javaworld.simplesocketclient; εισαγωγή java.io.BufferedReader; εισαγωγή java.io.I / OException; εισαγωγή java.io.InputStreamReader; εισαγωγή java.io.PrintWriter; εισαγωγή java.net.ServerSocket; εισαγωγή java.net.Socket; δημόσια τάξη SimpleSocketServer επεκτείνει το νήμα {private ServerSocket serverSocket. ιδιωτική θύρα int ιδιωτικό boolean running = false; δημόσιο SimpleSocketServer (int port) {this.port = port; } public void startServer () {try {serverSocket = new ServerSocket (θύρα); αυτό. ξεκινήστε (); } catch (I / OException e) {e.printStackTrace (); }} public void stopServer () {running = false; αυτό. διακοπή (); } @Override public void run () {running = true; ενώ (τρέχει) {δοκιμάστε το {System.out.println ("Ακρόαση σύνδεσης"); // Αποδοχή κλήσης () για λήψη της επόμενης σύνδεσης Socket socket = serverSocket.accept (); // Περάστε την υποδοχή στο νήμα RequestHandler για επεξεργασία RequestHandler requestHandler = new RequestHandler (socket); requestHandler.start (); } catch (I / OException e) {e.printStackTrace (); }}} δημόσιος στατικός κενός κενός (String [] args) {if (args.length == 0) {System.out.println ("Usage: SimpleSocketServer"); System.exit (0); } int port = Integer.parseInt (args [0]); System.out.println ("Έναρξη διακομιστή στη θύρα:" + θύρα); Διακομιστής SimpleSocketServer = νέος SimpleSocketServer (θύρα); server.startServer (); // Αυτόματη απενεργοποίηση σε 1 λεπτό δοκιμάστε το {Thread.sleep (60000). } catch (Εξαίρεση e) {e.printStackTrace (); } server.stopServer (); }} Το Class RequestHandler επεκτείνει το νήμα {private Socket socket; RequestHandler (Socket socket) {this.socket = socket; } @Override public void run () {try {System.out.println ("Λήφθηκε σύνδεση"); // Λήψη ροών εισόδου και εξόδου BufferedReader σε = νέο BufferedReader (νέο InputStreamReader (socket.getInputStream ())); PrintWriter out = νέο PrintWriter (socket.getOutputStream ()); // Γράψτε την κεφαλίδα μας στον πελάτη out.println ("Echo Server 1.0"); out.flush (); // Echo γραμμές πίσω στον πελάτη έως ότου ο πελάτης κλείσει τη σύνδεση ή λάβουμε μια κενή γραμμή String line = in.readLine (); while (line! = null && line.length ()> 0) {out.println ("Echo:" + γραμμή); out.flush (); line = in.readLine (); } // Κλείστε τη σύνδεσή μας στο.close (); out.close (); socket.close (); System.out.println ("Η σύνδεση έκλεισε"); } catch (Εξαίρεση e) {e.printStackTrace (); }}}