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

Δημιουργία συστήματος συνομιλίας μέσω Διαδικτύου

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

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

Δημιουργία πελάτη συνομιλίας

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

Η διεπαφή ChatClient

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

Πελάτης συνομιλίας τάξης

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

εισαγωγή java.net. *; εισαγωγή java.io. *; εισαγωγή java.awt. *; δημόσια τάξη Το ChatClient επεκτείνει το πλαίσιο υλοποίησης Runnable {// public ChatClient (τίτλος συμβολοσειράς, InputStream i, OutputStream o) ... // public void run () ... // public boolean handle static void main (String args []) ρίχνει το IOException ...} 

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

 προστατευμένο DataInputStream i; προστατευμένο DataOutputStream o; προστατευμένη έξοδος TextArea. προστατευμένη είσοδος TextField. προστατευμένο ακροατήριο νημάτων? δημόσιο ChatClient (τίτλος συμβολοσειράς, InputStream i, OutputStream o) {super (title); this.i = new DataInputStream (νέο BufferedInputStream (i)); this.o = νέο DataOutputStream (νέο BufferedOutputStream (o)); setLayout (νέο BorderLayout ()); προσθήκη ("Κέντρο", έξοδος = νέο TextArea ()); output.setEditable (false); add ("South", input = new TextField ()); πακέτο (); προβολή (); input.requestFocus (); listener = νέο νήμα (αυτό); listener.start (); } 

Ο κατασκευαστής παίρνει τρεις παραμέτρους: έναν τίτλο για το παράθυρο, μια ροή εισόδου και μια ροή εξόδου. ο Πελάτης συνομιλίας επικοινωνεί μέσω των καθορισμένων ροών. δημιουργούμε buffered streams i and o για να παρέχουμε αποτελεσματικές εγκαταστάσεις επικοινωνίας υψηλότερου επιπέδου σε αυτές τις ροές. Στη συνέχεια, δημιουργήσαμε το απλό περιβάλλον εργασίας χρήστη που αποτελείται από το Περιοχή κειμένου παραγωγή και το Πεδίο κειμένου εισαγωγή. Σχεδιάζουμε και δείχνουμε το παράθυρο και ξεκινάμε ένα Νήμα ακροατής που δέχεται μηνύματα από το διακομιστή.

public void run () {try {while (true) {String line = i.readUTF (); output.appendText (γραμμή + "\ n"); }} catch (IOException ex) {ex.printStackTrace (); } τελικά {listener = null; input.hide (); επικύρωση (); δοκιμάστε το {o.close (); } catch (IOException ex) {ex.printStackTrace (); }}} 

Όταν το νήμα ακροατή εισέρχεται στη μέθοδο εκτέλεσης, καθόμαστε σε μια ανάγνωση ατέλειωτου βρόχου Σειράs από τη ροή εισόδου. Όταν ένα Σειρά φτάνει, το προσαρτάμε στην περιοχή εξόδου και επαναλαμβάνουμε τον βρόχο. Ενα IOException θα μπορούσε να συμβεί εάν η σύνδεση με τον διακομιστή έχει χαθεί. Σε αυτήν την περίπτωση, εκτυπώνουμε την εξαίρεση και εκτελούμε εκκαθάριση. Σημειώστε ότι αυτό θα σηματοδοτηθεί από ένα EOFException από το readUTF () μέθοδος.

Για την εκκαθάριση, αναθέτουμε πρώτα την αναφορά ακροατή μας σε αυτό Νήμα προς την μηδενικό; Αυτό δείχνει στον υπόλοιπο κώδικα ότι το νήμα έχει τερματιστεί. Στη συνέχεια κρύβουμε το πεδίο εισαγωγής και καλούμε επικυρώνω() έτσι ώστε η διεπαφή να διαμορφωθεί ξανά και να κλείσει το Έξοδος ροής o για να βεβαιωθείτε ότι η σύνδεση είναι κλειστή.

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

public boolean handleEvent (Event e) {if ((e.target == input) && (e.id == Event.ACTION_EVENT)) {δοκιμάστε {o.writeUTF ((String) e.arg); o.flush (); } catch (IOException ex) {ex.printStackTrace (); listener.stop (); } input.setText (""); επιστροφή αληθινή? } αλλιώς εάν ((e.target == αυτό) && (e.id == Event.WINDOW_DESTROY)) {if (listener! = null) listener.stop (); κρύβω (); επιστροφή αληθινή? } επιστροφή super.handleEvent (e); } 

Στο handleEvent () μέθοδος, πρέπει να ελέγξουμε για δύο σημαντικά συμβάντα διεπαφής χρήστη:

Το πρώτο είναι ένα γεγονός δράσης στο Πεδίο κειμένου, που σημαίνει ότι ο χρήστης έχει πατήσει το πλήκτρο Return. Όταν πιάσουμε αυτό το συμβάν, γράφουμε το μήνυμα στη ροή εξόδου και μετά καλούμε ξεπλύνετε() για να διασφαλιστεί ότι αποστέλλεται αμέσως. Η ροή εξόδου είναι α DataOutputStream, έτσι μπορούμε να χρησιμοποιήσουμε εγγραφήUTF () για να στείλετε ένα Σειρά. Αν μία IOException συμβαίνει η σύνδεση πρέπει να έχει αποτύχει, οπότε σταματάμε το νήμα ακρόασης. Αυτό θα εκτελέσει αυτόματα όλο τον απαραίτητο καθαρισμό.

Το δεύτερο συμβάν είναι ο χρήστης που προσπαθεί να κλείσει το παράθυρο. Εναπόκειται στον προγραμματιστή να αναλάβει αυτήν την εργασία. σταματάμε το νήμα του ακροατή και κρύβουμε το Πλαίσιο.

public static void main (String args []) ρίχνει το IOException {if (args.length! = 2) ρίξτε νέο RuntimeException ("Syntax: ChatClient"); Socket s = new Socket (args [0], Integer.parseInt (args [1])); νέο ChatClient ("Chat" + args [0] + ":" + args [1], s.getInputStream (), s.getOutputStream ()); } 

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

Δημιουργία διακομιστή πολλαπλών νημάτων

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

Υπάρχουν δύο τάξεις σε αυτό το πρόγραμμα: η κύρια τάξη, Διακομιστής συνομιλίας, είναι ένας διακομιστής που δέχεται συνδέσεις από πελάτες και τους εκχωρεί σε νέα αντικείμενα χειριστή σύνδεσης. ο ChatHandler Το μάθημα κάνει πραγματικά το έργο της ακρόασης μηνυμάτων και της μετάδοσής τους σε όλους τους συνδεδεμένους πελάτες. Ένα νήμα (το κύριο νήμα) χειρίζεται νέες συνδέσεις και υπάρχει ένα νήμα (το ChatHandler τάξη) για κάθε πελάτη.

Κάθε νέο Πελάτης συνομιλίας θα συνδεθεί στο Διακομιστής συνομιλίας; Αυτό Διακομιστής συνομιλίας θα παραδώσει τη σύνδεση σε μια νέα παρουσία του ChatHandler τάξη που θα λαμβάνει μηνύματα από τον νέο πελάτη. Μέσα στο ChatHandler τάξη, διατηρείται μια λίστα με τους τρέχοντες χειριστές. ο αναμετάδοση() Η μέθοδος χρησιμοποιεί αυτήν τη λίστα για να μεταδώσει ένα μήνυμα σε όλους τους συνδεδεμένους Πελάτης συνομιλίαςμικρό.

Class ChatServer

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

εισαγωγή java.net. *; εισαγωγή java.io. *; εισαγωγή java.util. *; δημόσια τάξη ChatServer {// public ChatServer (int port) ρίχνει το IOException ... // public static void main (String args []) ρίχνει το IOException ...} 

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

 δημόσιο ChatServer (int port) ρίχνει IOException {ServerSocket server = νέο ServerSocket (θύρα); ενώ (true) {Socket client = server.accept (); System.out.println ("Αποδεκτό από" + client.getInetAddress ()); ChatHandler c = νέο ChatHandler (πελάτης); γ. έναρξη (); }} 

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

public static void main (String args []) ρίχνει το IOException {if (args.length! = 1) ρίξτε νέο RuntimeException ("Syntax: ChatServer"); νέο ChatServer (Integer.parseInt (args [0])); } 

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

Class ChatHandler

Αυτή η τάξη ασχολείται με το χειρισμό μεμονωμένων συνδέσεων. Πρέπει να λάβουμε μηνύματα από τον πελάτη και να τα στείλουμε ξανά σε όλες τις άλλες συνδέσεις. Διατηρούμε μια λίστα με τις συνδέσεις στο a

στατικός

Διάνυσμα.

εισαγωγή java.net. *; εισαγωγή java.io. *; εισαγωγή java.util. *; δημόσια τάξη Το ChatHandler επεκτείνει το νήμα {// το δημόσιο ChatHandler (Socket s) ρίχνει το IOException ... // public void run () ...} 

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

 προστατευμένο Socket s; προστατευμένο DataInputStream i; προστατευμένο DataOutputStream o; το δημόσιο ChatHandler (Socket s) ρίχνει το IOException {this.s = s; i = νέο DataInputStream (νέο BufferedInputStream (s.getInputStream ())); o = νέο DataOutputStream (νέο BufferedOutputStream (s.getOutputStream ())); } 

Ο κατασκευαστής διατηρεί μια αναφορά στην υποδοχή του πελάτη και ανοίγει μια ροή εισόδου και εξόδου. Και πάλι, χρησιμοποιούμε ρυθμισμένες ροές δεδομένων. Αυτά μας παρέχουν αποτελεσματικό I / O και μεθόδους για την επικοινωνία τύπων δεδομένων υψηλού επιπέδου - σε αυτήν την περίπτωση, Σειράμικρό.

προστατευμένο στατικό φορέα χειριστή = νέο διάνυσμα (); δημόσια άκυρη εκτέλεση () {try {handlers.addElement (this); ενώ (true) {String msg = i.readUTF (); μετάδοση (msg); }} catch (IOException ex) {ex.printStackTrace (); } τέλος {handlers.removeElement (αυτό); δοκιμάστε το {s.close (); } catch (IOException ex) {ex.printStackTrace (); }}} // προστατευμένη στατική κενή μετάδοση (String message) ... 

ο τρέξιμο() Η μέθοδος είναι όπου μπαίνει το νήμα μας. Πρώτα προσθέτουμε το νήμα μας στο Διάνυσμα του ChatHandlerχειριστές. Οι χειριστές Διάνυσμα διατηρεί μια λίστα με όλους τους τρέχοντες χειριστές. Είναι ένα στατικός μεταβλητή και έτσι υπάρχει μία παρουσία του Διάνυσμα Για το σύνολο ChatHandler τάξη και όλες τις παρουσίες της. Έτσι, όλα ChatHandlers μπορούν να έχουν πρόσβαση στη λίστα των τρεχουσών συνδέσεων.

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

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

προστατευμένη στατική κενή μετάδοση (μήνυμα συμβολοσειράς) {συγχρονισμένη (χειριστές) {Enumeration e = handlers.elements (); ενώ (e.hasMoreElements ()) {ChatHandler c = (ChatHandler) e.nextElement (); δοκιμάστε το {synchronized (c.o) {c.o.writeUTF (message); } c.o.flush (); } catch (IOException ex) {c.stop (); }}}} 

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

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