Προγραμματισμός

Προγραμματισμός Socket σε Java: Ένα σεμινάριο

Αυτό το σεμινάριο είναι μια εισαγωγή στον προγραμματισμό υποδοχών στην Java, ξεκινώντας με ένα απλό παράδειγμα πελάτη-διακομιστή που δείχνει τις βασικές δυνατότητες του Java I / O. Θα εισαχθείτε και στα δύο πρωτότυπαjava.io πακέτο και NIO, το μη αποκλεισμένο I / O (java.nioAPI που εισήχθησαν στην 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 ().

λήψη Κατεβάστε τον πηγαίο κώδικα Ο πηγαίος κώδικας για "Προγραμματισμός Socket σε Java: Ένας οδηγός." Δημιουργήθηκε από τον Steven Haines για το JavaWorld.

Παράδειγμα πελάτη Java socket

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

  1. Δημιουργήστε μια υποδοχή στον διακομιστή ιστού που ακούει στη θύρα 80.
  2. Αποκτήστε ένα PrintStream στον διακομιστή και στείλτε το αίτημα Λάβετε PATH HTTP / 1.0, όπου ΜΟΝΟΠΑΤΙ είναι ο πόρος που ζητήθηκε στον διακομιστή. Για παράδειγμα, εάν θέλαμε να ανοίξουμε τη ρίζα ενός ιστότοπου, τότε η διαδρομή θα ήταν /.
  3. Αποκτήστε ένα 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

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

  1. Δημιουργώ ένα Υποδοχή διακομιστή, καθορίζοντας μια θύρα για ακρόαση.
  2. Επικαλέστε το Υποδοχή διακομιστή'μικρό αποδέχομαι() μέθοδο για ακρόαση στη διαμορφωμένη θύρα για σύνδεση πελάτη.
  3. Όταν ένας πελάτης συνδέεται με το διακομιστή, το αποδέχομαι() η μέθοδος επιστρέφει a Πρίζα μέσω του οποίου ο διακομιστής μπορεί να επικοινωνήσει με τον πελάτη. Αυτό είναι το ίδιο Πρίζα τάξη που χρησιμοποιήσαμε για τον πελάτη μας, οπότε η διαδικασία είναι η ίδια: αποκτήστε ένα InputStream για να διαβάσετε από τον πελάτη και ένα Έξοδος ροής γράψτε στον πελάτη.
  4. Εάν ο διακομιστής σας πρέπει να είναι επεκτάσιμος, θα θελήσετε να περάσετε το Πρίζα σε άλλο νήμα για επεξεργασία, ώστε ο διακομιστής σας να μπορεί να συνεχίσει να ακούει για επιπλέον συνδέσεις.
  5. Καλέστε το Υποδοχή διακομιστή'μικρό αποδέχομαι() μέθοδος ξανά για να ακούσετε μια άλλη σύνδεση.

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

 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 (); }}}