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

Απλός χειρισμός των χρονικών ορίων του δικτύου

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

Όταν εργάζεστε με συνδέσεις δικτύου ή οποιονδήποτε τύπο συσκευής I / O, υπάρχουν δύο ταξινομήσεις λειτουργιών:

  • Λειτουργίες αποκλεισμού: Διαβάστε ή γράψτε πάγκους, η λειτουργία περιμένει έως ότου η συσκευή I / O είναι έτοιμη
  • Λειτουργίες χωρίς αποκλεισμό: Έχει γίνει προσπάθεια ανάγνωσης ή εγγραφής, η λειτουργία διακόπτεται εάν η συσκευή I / O δεν είναι έτοιμη

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

Αυτή η λύση χρησιμοποιείται συχνά, αλλά υπάρχει μια πολύ απλούστερη εναλλακτική λύση. Η Java υποστηρίζει επίσης δίκτυο αποκλεισμού I / O, το οποίο μπορεί να ενεργοποιηθεί σε οποιοδήποτε Πρίζα, Υποδοχή διακομιστή, ή Υποδοχή. Είναι δυνατό να προσδιορίσετε το μέγιστο χρονικό διάστημα που θα σταματήσει η λειτουργία ανάγνωσης ή εγγραφής πριν επιστρέψετε το στοιχείο ελέγχου στην εφαρμογή. Για πελάτες δικτύου, αυτή είναι η ευκολότερη λύση και προσφέρει απλούστερο, πιο εύχρηστο κώδικα.

Το μόνο μειονέκτημα του δικτύου αποκλεισμού I / O στην Java είναι ότι απαιτεί μια υπάρχουσα πρίζα. Έτσι, ενώ αυτή η μέθοδος είναι ιδανική για κανονικές λειτουργίες ανάγνωσης ή εγγραφής, μια λειτουργία σύνδεσης μπορεί να σταματήσει για πολύ μεγαλύτερο χρονικό διάστημα, καθώς δεν υπάρχει μέθοδος για τον καθορισμό χρονικού ορίου για λειτουργίες σύνδεσης. Πολλές εφαρμογές απαιτούν αυτήν την ικανότητα. Μπορείτε, ωστόσο, να αποφύγετε εύκολα το επιπλέον έργο της σύνταξης πρόσθετου κώδικα. Έχω γράψει μια μικρή τάξη που σας επιτρέπει να καθορίσετε μια τιμή χρονικού ορίου για μια σύνδεση. Χρησιμοποιεί ένα δεύτερο νήμα, αλλά οι εσωτερικές λεπτομέρειες αφαιρούνται. Αυτή η προσέγγιση λειτουργεί καλά, καθώς παρέχει μια διασύνδεση εισόδου / εξόδου χωρίς αποκλεισμό, και οι λεπτομέρειες του δεύτερου νήματος κρύβονται από την προβολή.

Μη αποκλεισμός I / O δικτύου

Ο απλούστερος τρόπος να κάνετε κάτι αποδεικνύεται συχνά ο καλύτερος τρόπος. Ενώ μερικές φορές είναι απαραίτητο να χρησιμοποιηθούν νήματα και να μπλοκάρουν το I / O, στις περισσότερες περιπτώσεις το μη αποκλεισμό I / O προσφέρεται για μια πολύ πιο καθαρή και πιο κομψή λύση. Με λίγες μόνο γραμμές κώδικα, μπορείτε να ενσωματώσετε υποστηρίξεις χρονικού ορίου για οποιαδήποτε εφαρμογή socket. Δεν με πιστεύεις; Συνέχισε να διαβάζεις.

Όταν κυκλοφόρησε το Java 1.1, περιελάμβανε αλλαγές API στο java.net πακέτο που επέτρεψε στους προγραμματιστές να καθορίσουν επιλογές υποδοχής. Αυτές οι επιλογές παρέχουν στους προγραμματιστές μεγαλύτερο έλεγχο στην επικοινωνία με την υποδοχή. Μια επιλογή ειδικότερα, ΩΡΑ_ ΤΙΜΗ, είναι εξαιρετικά χρήσιμο, επειδή επιτρέπει στους προγραμματιστές να καθορίσουν το χρονικό διάστημα που θα αποκλείσει μια λειτουργία ανάγνωσης. Μπορούμε να καθορίσουμε μια μικρή καθυστέρηση, ή καθόλου, και να καταστήσουμε τον αποκλεισμό του κώδικα δικτύωσης.

Ας ρίξουμε μια ματιά στο πώς λειτουργεί αυτό. Μια νέα μέθοδος, setSoTimeout (int) έχει προστεθεί στις ακόλουθες τάξεις υποδοχών:

  • java.net.Socket
  • java.net.DatagramSocket
  • java.net.ServerSocket

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

  • ServerSocket.accept ()
  • SocketInputStream.read ()
  • DatagramSocket.receive ()

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

// Δημιουργήστε μια υποδοχή datagram στη θύρα 2000 για να ακούσετε εισερχόμενα πακέτα UDP DatagramSocket dgramSocket = νέο DatagramSocket (2000); // Απενεργοποίηση αποκλεισμού λειτουργιών εισόδου / εξόδου, καθορίζοντας χρονικό όριο πέντε δευτερολέπτων dgramSocket.setSoTimeout (5000). 

Ο καθορισμός μιας τιμής χρονικού ορίου αποτρέπει τον αποκλεισμό των λειτουργιών του δικτύου μας επ 'αόριστον. Σε αυτό το σημείο, πιθανώς αναρωτιέστε τι θα συμβεί όταν λήξει μια λειτουργία δικτύου. Αντί να επιστρέφει έναν κωδικό σφάλματος, ο οποίος ενδέχεται να μην ελέγχεται πάντα από προγραμματιστές, java.io.InterruptIOException ρίχνεται. Ο χειρισμός εξαίρεσης είναι ένας εξαιρετικός τρόπος αντιμετώπισης των συνθηκών σφάλματος και μας επιτρέπει να διαχωρίσουμε τον κανονικό κώδικα από τον κώδικα χειρισμού σφαλμάτων. Εκτός αυτού, ποιος ελέγχει θρησκευτικά κάθε τιμή επιστροφής για μηδενική αναφορά; Πετώντας μια εξαίρεση, οι προγραμματιστές αναγκάζονται να παρέχουν ένα χειριστή αλιευμάτων για χρονικά όρια.

Το παρακάτω απόσπασμα κώδικα δείχνει τον τρόπο χειρισμού μιας λειτουργίας χρονικού ορίου κατά την ανάγνωση από μια υποδοχή TCP:

// Ρυθμίστε το χρονικό όριο της υποδοχής για σύνδεση δέκα δευτερολέπτων. SetSoTimeout (10000); δοκιμάστε {// Δημιουργία DataInputStream για ανάγνωση από το socket DataInputStream din = new DataInputStream (connection.getInputStream ()); // Διαβάστε τα δεδομένα έως το τέλος των δεδομένων για (;;) {String line = din.readLine (); if (line! = null) System.out.println (γραμμή); αλλιώς σπάσει? }} // Η εξαίρεση ρίχνεται όταν προκύψει χρονικό όριο δικτύου (InterruptIOException iioe) {System.err.println ("Χρονικό όριο απομακρυσμένου κεντρικού υπολογιστή κατά τη λειτουργία ανάγνωσης"); } // Η εξαίρεση ρίχνεται όταν συμβαίνει γενικό σφάλμα I / O δικτύου (IOException) {System.err.println ("Σφάλμα I / O δικτύου -" + αι); } 

Με μόνο μερικές επιπλέον γραμμές κώδικα για ένα δοκιμάστε {} catch block, είναι εξαιρετικά εύκολο να πιάσετε χρονικά όρια δικτύου. Μια εφαρμογή μπορεί στη συνέχεια να ανταποκριθεί στην κατάσταση χωρίς να σταματήσει. Για παράδειγμα, θα μπορούσε να ξεκινήσει ειδοποιώντας τον χρήστη ή προσπαθώντας να δημιουργήσει μια νέα σύνδεση. Όταν χρησιμοποιείτε πρίζες δεδομένων, οι οποίες στέλνουν πακέτα πληροφοριών χωρίς εγγύηση παράδοσης, μια εφαρμογή θα μπορούσε να ανταποκριθεί σε ένα χρονικό όριο δικτύου αποστέλλοντας ξανά ένα πακέτο που είχε χαθεί κατά τη μεταφορά. Η εφαρμογή αυτής της υποστήριξης χρονικού ορίου απαιτεί πολύ λίγο χρόνο και οδηγεί σε μια πολύ καθαρή λύση. Πράγματι, η μόνη στιγμή που το μη αποκλεισμό I / O δεν είναι η βέλτιστη λύση είναι όταν πρέπει επίσης να ανιχνεύσετε χρονικά όρια στις λειτουργίες σύνδεσης ή όταν το περιβάλλον προορισμού σας δεν υποστηρίζει Java 1.1.

Χρονικό όριο χειρισμού σε εργασίες σύνδεσης

Εάν ο στόχος σας είναι να επιτύχετε πλήρη ανίχνευση και χειρισμό χρονικού ορίου, τότε θα πρέπει να εξετάσετε τις λειτουργίες σύνδεσης. Κατά τη δημιουργία μιας παρουσίας του java.net.Socket, γίνεται προσπάθεια δημιουργίας σύνδεσης. Εάν ο κεντρικός υπολογιστής είναι ενεργός, αλλά δεν εκτελείται καμία υπηρεσία στη θύρα που καθορίζεται στο java.net.Socket κατασκευαστής, α ConnectionException θα πεταχτεί και ο έλεγχος θα επιστρέψει στην εφαρμογή. Ωστόσο, εάν το μηχάνημα είναι εκτός λειτουργίας, ή εάν δεν υπάρχει διαδρομή προς αυτόν τον κεντρικό υπολογιστή, η σύνδεση της πρίζας τελικά θα τερματιστεί από μόνη της πολύ αργότερα. Στο μεταξύ, η εφαρμογή σας παραμένει παγωμένη και δεν υπάρχει τρόπος να αλλάξετε την τιμή του χρονικού ορίου.

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

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

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

// Σύνδεση σε απομακρυσμένο διακομιστή με όνομα κεντρικού υπολογιστή, με χρονικό όριο τεσσάρων δευτερολέπτων Socket σύνδεση = TimedSocket.getSocket ("server.my-network.net", 23, 4000); 

Εάν όλα πάνε καλά, θα επιστραφεί μια πρίζα, όπως ακριβώς και το πρότυπο java.net.Socket κατασκευαστές. Εάν η σύνδεση δεν μπορεί να πραγματοποιηθεί πριν από το καθορισμένο χρονικό όριο, η μέθοδος θα σταματήσει και θα ρίξει ένα java.io.InterruptIOException, όπως και άλλες λειτουργίες ανάγνωσης υποδοχών όταν είχε καθοριστεί ένα χρονικό όριο χρησιμοποιώντας ένα setSoTimeout μέθοδος. Πολύ εύκολο, ε;

Ενσωμάτωση κώδικα πολλαπλών νημάτων σε μία κλάση

Ενώ το TimedSocket Το μάθημα είναι ένα χρήσιμο συστατικό από μόνο του, είναι επίσης ένα πολύ καλό εκπαιδευτικό βοήθημα για την κατανόηση του τρόπου αντιμετώπισης του αποκλεισμού I / O. Όταν εκτελείται μια λειτουργία αποκλεισμού, μια εφαρμογή με ένα σπείρωμα θα μπλοκαριστεί επ 'αόριστον. Εάν χρησιμοποιούνται πολλά νήματα εκτέλεσης, ωστόσο, μόνο ένα νήμα χρειάζεται καθυστέρηση. το άλλο νήμα μπορεί να συνεχίσει να εκτελείται. Ας ρίξουμε μια ματιά στο πώς το TimedSocket τάξη λειτουργεί.

Όταν μια εφαρμογή πρέπει να συνδεθεί σε έναν απομακρυσμένο διακομιστή, επικαλείται το TimedSocket.getSocket () μέθοδος και περνά λεπτομέρειες του απομακρυσμένου κεντρικού υπολογιστή και της θύρας. ο getSocket () Η μέθοδος είναι υπερφορτωμένη, επιτρέποντας και τα δύο α Σειρά όνομα κεντρικού υπολογιστή και ένα Διεύθυνση Inet να καθοριστεί. Αυτό το εύρος παραμέτρων θα πρέπει να είναι αρκετό για την πλειονότητα των λειτουργιών υποδοχής, αν και μπορεί να προστεθεί προσαρμοσμένη υπερφόρτωση για ειδικές εφαρμογές. μεσα στην getSocket () μέθοδος, δημιουργείται ένα δεύτερο νήμα.

Το ευφάνταστο όνομα SocketThread θα δημιουργήσει μια παρουσία του java.net.Socket, που μπορεί δυνητικά να μπλοκάρει για σημαντικό χρονικό διάστημα. Παρέχει βοηθητικές μεθόδους για να προσδιορίσει εάν έχει δημιουργηθεί σύνδεση ή εάν έχει προκύψει σφάλμα (για παράδειγμα, εάν java.net.SocketException ρίχτηκε κατά τη σύνδεση).

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

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

Το παρακάτω απόσπασμα κώδικα εμφανίζει τον μηχανισμό ψηφοφορίας και τον κωδικό αντιμετώπισης σφαλμάτων.

για (;;) {// Ελέγξτε για να δείτε εάν έχει δημιουργηθεί σύνδεση εάν (st.isConnected ()) {// Ναι ... εκχώρηση σε μεταβλητή κάλτσας και έξοδος από βρόχο sock = st.getSocket (); Διακοπή; } αλλιώς {// Ελέγξτε για να δείτε εάν προέκυψε σφάλμα εάν (st.isError ()) {// Δεν ήταν δυνατή η δημιουργία σύνδεσης (st.getException ()); } δοκιμάστε {// Sleep για μικρό χρονικό διάστημα Thread.sleep (POLL_DELAY); } catch (InterruptException δηλαδή) {} // Χρονοδιακόπτης αύξησης + = POLL_DELAY; // Ελέγξτε για να δείτε αν ξεπεράστηκε το χρονικό όριο εάν (χρονόμετρο> καθυστέρηση) {// Δεν είναι δυνατή η σύνδεση στο διακομιστή ρίξτε νέο InterruptIOException ("Δεν ήταν δυνατή η σύνδεση για" + καθυστέρηση + "χιλιοστά του δευτερολέπτου"); }}} 

Μέσα στο μπλοκαρισμένο νήμα

Ενώ η σύνδεση υποβάλλεται σε τακτική δημοσκόπηση, το δεύτερο νήμα επιχειρεί να δημιουργήσει μια νέα παρουσία του java.net.Socket. Παρέχονται μέθοδοι προσπέλασης για τον προσδιορισμό της κατάστασης της σύνδεσης, καθώς και για τη λήψη της τελικής σύνδεσης υποδοχής. ο SocketThread.isConnected () Η μέθοδος επιστρέφει μια δυαδική τιμή για να δείξει εάν έχει δημιουργηθεί μια σύνδεση και το SocketThread.getSocket () η μέθοδος επιστρέφει a Πρίζα. Παρέχονται παρόμοιες μέθοδοι για να προσδιοριστεί εάν έχει προκύψει σφάλμα και για πρόσβαση στην εξαίρεση που εντοπίστηκε.

Όλες αυτές οι μέθοδοι παρέχουν μια ελεγχόμενη διεπαφή στο SocketThread Για παράδειγμα, χωρίς να επιτρέπεται η εξωτερική τροποποίηση ιδιωτικών μεταβλητών μελών Το ακόλουθο παράδειγμα κώδικα δείχνει το νήμα τρέξιμο() μέθοδος. Πότε και εάν, ο κατασκευαστής υποδοχής επιστρέφει a Πρίζα, θα εκχωρηθεί σε μια μεταβλητή ιδιωτικού μέλους, στην οποία οι μέθοδοι πρόσβασης παρέχουν πρόσβαση. Την επόμενη φορά που θα ερωτηθεί μια κατάσταση σύνδεσης, χρησιμοποιώντας το SocketThread.isConnected () μέθοδος, η υποδοχή θα είναι διαθέσιμη για χρήση. Η ίδια τεχνική χρησιμοποιείται για τον εντοπισμό σφαλμάτων. αν ένα java.io.IOException έχει πιάσει, θα αποθηκευτεί σε ένα ιδιωτικό μέλος, στο οποίο μπορείτε να αποκτήσετε πρόσβαση μέσω του isError () και getException () βοηθητικές μέθοδοι.