Αντικειμενοστραφής Προγραμματισμός I (5 ο εξ) Διάλεξη #11 η : Διαδικτυακές Εφαρμογές με τη Java Γαβαλάς Δαμιανός dgavalas@aegean.gr Όροι IP διεύθυνση (IP address) Ένας 32 bit αριθμός που χρησιμοποιείται από το Internet Protocol (IP) για την παράδοση δεδομένων δ στο σωστό Η/Υ στο δίκτυο Μια IP διεύθυνση προσδιορίζει μοναδικά ένα Η/Υ στο Internet Θύρα (Port) 16 bit αριθμός που χρησιμοποιείται από το Transmission Control Protocol (TCP) και το User Datagram Protocol (UDP) για την παράδοση δεδομένων στη σωστή εφαρμογή Π.χ. ένα εισερχόμενο πακέτο δεδομένων θα παραδοθεί σε ένα chat application που χρησιμοποιεί ο χρήστης και όχι στο browser που επίσης χρησιμοποιεί ταυτόχρονα ο χρήστης Ένας αριθμός θύρας προσδιορίζει μοναδικά μια εφαρμογή σε έναν Η/Υ 1
Όροι Υποδοχή (Socket) Είναι μια αμφίδρομη σύνδεση επικοινωνίας μεταξύ δύο εφαρμογών που τρέχουν στο δίκτυο. Μια υποδοχή αντιστοιχεί σε μία θύρα σε έναν Η/Υ Χρησιμοποιείται ως τερματικό σημείο για την ανταλλαγή δεδομένων μεταξύ δυο Η/Υ Διεύθυνση υποδοχής Σε κάθε υποδοχή αντιστοιχεί μια διεύθυνση. Αυτή είναι ένας συνδυασμός της IP διεύθυνσης κι ενός μοναδικού αριθμο υ θύρας. Μια διεύθυνση υποδοχής χαρακτηρίζει μοναδικά μια εφαρμογή στο δίκτυο Όροι UDP User Datagram Protocol, είναι ένα πρωτόκολλο του στρώματος ςμεταφοράς (transport layer), υπεύθυνο για να μεταφέρει πακέτα δεδομένων από μια δικτυακή εφαρμογή σε μια άλλη Datagram Καλείται ένα πακέτο του UDP. Είναι ένα ανεξάρτητο μικρού μεγέθους δεδομένο που αποστέλλεται στο δίκτυο και δεν εγγυάται κανείς ούτε για την ακεραιότητα του περιεχομένου του ούτε για την παράδοσή του 2
Εγκατάσταση σύνδεσης Άρα, μια υποδοχή (socket) αποτελεί το ένα άκρο ενός αμφίδρομου καναλιού επικοινωνίας μεταξύ δύο εφαρμογών που εκτελούνται σε κάποιο δίκτυο. Κάθε υποδοχή αντιστοιχίζεται σε ένα αριθμό πόρτας (port number) που χρησιμοποιείται από το στρώμα TCP (TCP layer) για να αναγνωριστεί η εφαρμογή στην οποία στέλνεται ένα TCP πακέτο. Διαδικασία εγκατάστασης σύνδεσης μεταξύ client και server: Server connection Client Server Client Port connection Επικοινωνία πελάτη/διακομιστή (Client/Server) Η εφαρμογή του διακομιστή πρέπει να «τρέχει» όταν ο πελάτης ξεκινάει την επικοινωνία. Ο διακομιστής περιμένει για κάποια αίτηση σύνδεσης (connection request) από πελάτη. Για να προγραμματίσεις έναν διακομιστή, πρέπει να δημιουργήσεις μία υποδοχή διακομιστή (server socket) και να τη συσχετίσεις με μία θύρα (port), στην οποία ο διακομιστής «ακούει» για εισερχόμενες αιτήσεις σύνδεσης. Αφότου ο διακομιστής αποδεχθεί τη σύνδεση, η επικοινωνία πελάτηδιακομιστή διεξάγεται όπως ακριβώς με κάθε ρεύμα δεδομένων εισόδου/εξόδου (I/O stream). Αφότου δημιουργηθεί μια υποδοχή διακομιστή, ο διακομιστής χρησιμοποιεί αυτή τη δήλωση για να «ακούει» για εισερχόμενες αιτήσεις σύνδεσης. Υπολογιστής διακομιστή (Server Host) Server socket on port 8000 SeverSoc ket serve r = new ServerSocket(8000); Υποδοχή δι ακομισ τή (server socket) Socket socket = server.accept() Ρεύμα δεδομένων (I/O Stream ) Υπολογιστής πελάτη (Client Host) Υποδοχή πελάτη (Client socket) Sock et socket = new Socket(host, 8000) Με αυτή τη δήλωση ο πελάτης στέλνει μια αίτηση σύνδεσης στο διακομιστή 3
Μετάδοση δεδομένων μέσω υποδοχών Διακομιστής (Server) Πελάτης (Client) int port = 8000; DataInputStream in; DtOt DataOutputStream tst out; ServerSocket server; Socket socket; server =new ServerSocket(port); socket=server.accept(); in=new DataInputStream (socket.getinputstream()); out=new DataOutStream (socket.getoutputstream()); System.out.println(in.readDouble()); out.writedouble(anumber); Αίτηση σύνδεσης I/O ρεύματα int port = 8000; String host="localhost" DtI DataInputStream tst in; DataOutputStream out; Socket socket; socket=new Socket(host, port); in=new DataInputStream (socket.getinputstream()); out=new DataOutputStream (socket.getoutputstream()); out.writedouble(anumber); System.out.println(in.readDouble()); InputStream input = socket.getinputstream(); OutputStream output = socket.getoutputstream(); Παράδειγμα επικοινωνίας πελάτη διακομιστή Γράψτε το πρόγραμμα ενός πελάτη που στέλνει δεδομένα σε έναν διακομιστή. Ο διακομιστής λαμβάνει τα δεδομένα, τα χρησιμοποιεί για να παράγει ένα αποτέλεσμα, και στη συνέχεια αποστέλλει το αποτέλεσμα πίσω στον πελάτη. Ο πελάτης εμφανίζει το αποτέλεσμα στην κονσόλα. Σε αυτό το παράδειγμα, τα δεδομένα που στέλνονται από τον πελάτη είναι η ακτίνα ενός κύκλου, και το αποτέλεσμα που παράγει και επιστρέφει ο διακομιστής είναι η επιφάνεια του κύκλου Περιοχή υπολογισμού Server ακτίνα (radiu s) επιφ άνεια (area) Client 4
Παράδειγμα επικοινωνίας πελάτη διακομιστή (συν.) Serv er Client Server Clien t radius radius area area DataInputStream DataOutputStream DataOutputStream DataOutputStream socket.getinputstream socket.getoutputstream socket.getoutputstream socket.get OutputSt ream socket socket socket socket Δίκτυο Δίκτυο (A) (B) Παράδειγμα επικοινωνίας πελάτη διακομιστή (συν.) Περιοχή υπολογισμού Server ακτίνα (radiu s) επιφ άνεια (area) Client 5
Παράδειγμα επικοινωνίας πελάτη διακομιστή (συν.): Διακομιστής try { // Create a server socket ServerSocket serversocket = new ServerSocket(8000); // Listen for a connection request Socket socket = serversocket.accept(); // Create data input and output streams DataInputStream inputfromclient = new DataInputStream(socket.getInputStream()); double radius = inputfromclient.readdouble(); Circle c = new Circle(radius); System.out.println("Εμβαδόν: " + c.getarea()); catch (IOException ex) { System.err.println(ex); Παράδειγμα επικοινωνίας πελάτη διακομιστή (συν.): Πελάτης try { // Create a socket to connect to the server Socket socket = new Socket("localhost", 8000); // Create an output stream to send data to the server DataOutputStream toserver = new DataOutputStream(socket.getOutputStream()); toserver.writedouble(10.0); catch (IOException ex) { System.err.println(ex.toString() + '\n'); 6
Η κλάση InetAddress Σε κάποιες περιπτώσεις μπορεί να θέλεις να γνωρίζεις ποιος συνδέθηκε στο διακομιστή. Μπορείς να χρησιμοποιήσεις την κλάση InetAddress για να βρεις το όνομα υπολογιστή και την IP διεύθυνση του πελάτη. Ηκλάση InetAddress μοντελοποιεί μια IP διεύθυνση Μπορείς να χρησιμοποιήσεις τη δήλωση που φαίνεται παρακάτω για να δημιουργήσεις ένα στιγμιότυπο της InetAddress του πελάτη σε μια υποδοχή InetAddress inetaddress = socket.getinetaddress(); Η κλάση InetAddress (συν.) Στη συνέχεια, μπορείς να εμφανίσεις το όνομα υπολογιστή και την IP διεύθυνση του πελάτη, ως ακολούθως: System.out.println("Το όνομα υπολογιστή του πελάτη είναι " + System.out.println( Το όνομα υπολογιστή του πελάτη είναι + inetaddress.gethostname()); System.out.println("Η IP διεύθυνση είναι " + inetaddress.gethostaddress()); 7
Εξυπηρετώντας πολλαπλούς πελάτες Συχνά, πολλαπλοί πελάτες σε έναν διακομιστή ταυτόχρονα. Τυπικά, ο διακομιστής εκτελείται συνεχώς σε έναν Η/Υ, και πελάτες από όλο το Internet μπορεί να θελήσουν να συνδεθούν σε αυτό. Μπορείτε να χρησιμοποιήσετε νήματα για να χειριστείτε τους πολλαπλούς πελάτες του διακομιστή ταυτόχρονα. Απλώς δημιουργήστε ένα νήμα για κάθε σύνδεση. Εδώ φαίνεται πως ο διακομιστής χειρίζεται την εγκατάσταση μιας σύνδεσης: while (true) { Socket socket = serversocket.accept(); Thread thread = new ThreadClass(socket); thread.start(); Η υποδοχή του διακομιστή μπορεί να έχει πολλές συνδέσεις. Σε κάθε επανάληψη του βρόγχου while δημιουργείται μια νέα σύνδεση. Όποτε εγκαθιδρύεται μια νέα σύνδεση, ένα νέο νήμα δημιουργείται για να χειριστεί την επικοινωνία μεταξύ του διακομιστή και του νέου πελάτη. Αυτό επιτρέπει την παράλληλη ύπαρξη πολλαπλών συνδέσεων. Παράδειγμα: Περνώντας αντικείμενα σε δικτυακές εφαρμογές Γράψτε ένα παράδειγμα όπου ένας πελάτης δημιουργεί ένα αντικείμενο της κλάσης Circle και το στέλνει στο διακομιστή. Η πληροφορία ρ θα πρέπει να περάσει ως αντικείμενο (object) Serv er Circle object Client Circle object i n.readobject() out.writeobject(student) in: ObjectInputStream out: ObjectOutputStream socket.getinputstream socket.getoutputstream socket socket Net work Σημείωση: Πρέπει να ξεκινήσει πρώτα ο διακομιστής και μετά ο πελάτης 8
Περνώντας αντικείμενα σε δικτυακές εφαρμογές: Η κλάση αντικείμενα της οποία στέλνονται public class Circle implements java.io.serializable { double radius; Αυτή η προσθήκη είναι απαραίτητη αν θέλουμε public Circle (double r) { αντικείμενα αυτής της radius = r; κλάσης να σταλούν δικτυακά σε άλλη εφαρμογή (ή και να αποθηκευθούν σε αρχείο) public String getarea () { return ("Εμβαδόν β κύκλου: " + (Math.PI PI* radius * radius)); Περνώντας αντικείμενα σε δικτυακές εφαρμογές: Διακομιστής try { // Create a server socket ServerSocket serversocket = new ServerSocket(8000); // Listen for a connection request Socket socket = serversocket.accept(); // Create data input and output streams ObjectInputStream inputfromclient = new ObjectInputStream(socket.getInputStream()); Circle c = (Circle)inputFromClient.readObject(); System.out.println("Εμβαδόν: " + c.getarea()); catch (Exception ex) { System.err.println(ex); 9
Περνώντας αντικείμενα σε δικτυακές εφαρμογές: Πελάτης try { // Create a socket to connect to the server Socket socket = new Socket("localhost", 8000); // Create an output stream to send data to the server ObjectOutputStream toserver = new ObjectOutputStream(socket.getOutputStream()); Circle c = new Circle(10); toserver.writeobject(c); t() catch (IOException ex) { System.err.println(ex.toString() + '\n'); Ανακτώντας αρχεία από Web διακομιστές Η Java σας δίνει τη δυνατότητα να αναπτύξετε εφαρμογές πελατών που ανακτούν αρχεία από απομακρυσμένους Η/Υ μέσω ενός Web διακομιστή. Σε αυτή την περίπτωση δεν χρειάζεται να δημιουργήστε μία «δική» σας εφαρμογή διακομιστή. Ο Web διακομιστής μπορεί να χρησιμοποιηθεί για να στείλει αρχεία στους πελάτες. Web διακομιστής Internet Web Browser Εφαρμ ογή που δι αβάζει το αρχείο Τοπικό αρχείο Εφαρμογή που διαβάζει το αρχείο 10
Η κλάση URL Ο ήχος και οι εικόνες αποθηκεύονται σε αρχεία. Η κλάση java.net.url χρησιμοποιείται για να προσδιορίσει τα αρχεία στο Internet. Γενικά, μια URL (Uniform Resource Locator) αποτελεί έναν δείκτη σε κάποιον «πόρο» του World Wide Web. Ένας πόρος μπορεί να είναι κάτι τόσο απλό όσο ένα αρχείο ή ένας φάκελος. Μπορείτε να δημιουργείτε URL αντικείμενα χρησιμοποιώντας τον παρακάτω κατεσκευαστή: public URL(String spec) throws MalformedURLException Για παράδειγμα, η ακόλουθη δήλωση δημιουργεί ένα URL αντικείμενο για τη διεύθυνση http://www.aegean.gr: try { URL url = new URL("http://www http://www.aegean.gr gr"); catch(malformedurlexception ex) { Στη συνέχεια χρησιμοποιείται η μέθοδος openstream() της κλάσης URL για να ανοίξει ένα ρεύμα εισόδου με τη URL του αρχείου: InputStream inputstream = url.openstream(); Παράδειγμα: ανακτώντας απομακρυσμένα αρχεία Αυτό το παράδειγμα επιδεικνύει πως ανακτάται ένα αρχείο από έναν Web server. Το πρόγραμμα εκτελείται ως εφαρμογή ή ως applet. Η γραφική διεπαφή περιλαμβάνει ένα text field στο οποίο πληκτρολογείται η URL του αρχείου, μια text area όπου θα εμφανιστεί το περιεχόμενο του αρχείου, και ένα πλήκτρο που χρησιμοποιείται για να υποβάλει τη URL. Μια label στο κάτω μέρος του applet δείχνει το status, π.χ. Το αρχείο φορτώθηκε επιτυχώς or Η URL δεν βρέθηκε. 11
Παράδειγμα: ανακτώντας απομακρυσμένα αρχεία try { // Διάβασε τη URL από το text field url = new URL(jtfURL.getText().trim()); // Δημιούργησε ένα ρεύμα εισόδου (buffered stream) InputStream is = url.openstream(); infile = new BufferedReader(new InputStreamReader(is)); String inline; // Διάβασε μι;α γραμμή και τοποθέτησέ τη στο τέλος της text area while ((inline = infile.readline())!= null) { jtafile.append(inline + '\n'); jlblstatus.settext("το αρχείο φορτώθηκε επιτυχώς"); catch (FileNotFoundException e) { jlblstatus.settext(" Η URL " + url + " δεν βρέθηκε."); Η κλάση JEditorPane Το πακέτο Swing παρέχει ένα γραφικό στοιχείο (GUI component) που ονομάζεται javax.swing.jeditorpane και χρησιμοποιείται για να εμφανίζει απλό text, HTML, και RTF αρχεία αυτόματα. Έτσι, δεν χρειάζεται αν γράφετε κώδικα που να διαβάζει δεδομένα από αρχεία. Η JEditorPane είναι υποκλάση της JTextComponent. Άρα κληρονομεί τις ιδιότητες και μεθόδους της JTextComponent. Για να εμφανίσετε το περιεχόμενο ενός αρχείου, χρησιμοποιείτε τη μέθοδο setpage(url) ως εξής: public void setpage(url url) throws IOException Η JEditorPane πυροδοτεί ένα javax.swing.event.hyperlinkevent όταν γίνεται κλικ πάνω σε ένα hyperlink που εμφανίζεται στο editor pane. Μέσω αυτού του συμβάντος, λαμβάνεται τη URL αυτού του hyperlink και εμφανίζεται χρησιμοποιώντας τη μέθοδο setpage(url). 12
Άσκηση για το σπίτι: Δημιουργήστε ένα Web Browser στη Java Υλοποιήστε έναν περιηγητή ιστού (web browser) Εμφάνιση HTML αρχείων χρησιμοποιώντας την κλάση JEditorPane. Stream Socket vs. Datagram Socket Stream socket Ένα αφοσιωμένο (dedicated) σημείο προς σημείο κανάλι μεταξύ ενός πελάτη και ενός διακομιστή. Χρήση TCP (Transmission Control Protocol) για τη μεταφορά δεδομένων. Εγγυάται την παράδοση και ακεραιότητα δεδομένων Αποστολή και λήψη πακέτων στην ίδια σειρά Datagram Δεν υπάρχει αφοσιωμένο σημείο προς σημείο κανάλι socket μεταξύ πελάτη διακομιστή. Χρήση UDP (User Datagram Protocol) για τη μεταφορά δεδομένων. Υπάρχει πιθανότητα απώλειας δεδομένων (δεν είναι 100% αξιόπιστο). Τα δεδομένα ενδέχεται να παραληφθούν σε διαφορετική σειρά από εκείνη που εκπέμφθηκαν 13
Η κλάση DatagramPacket Η κλάση DatagramPacket αναπαριστά ένα πακέτο datagram. Τα datagram χρησιμοποιούνται για να υλοποιήσουν υπηρεσία παράδοσης πακέτων χωρίς σύνδεση (connectionless). Κάθε μήνυμα δρομολογείται από έναν Η/Υ σε κάποιον άλλο βάσει αποκλειστικά της πληροφορίας που περιέχεται στο πακέτο java.net.datagrampacket length: int address: InetAddress port: in t Ορίζει το μήκος (μέγεθος) του bu ffer. Προσδιορίζει τη διεύθυνση του Η/Υ όπου το πακέτο στέλνεται ή λαμβάνεται Προσδιορίζει τη θύρα του Η/Υ όπου το πακ έτο στέλνεται ή λα μβάνετα ι +DatagramPacket(buf: byte[], length: int, host: InetAddress, port: int) Δημιουργεί ένα datagram από ένα byte array buf προσδιορισμένου length, διεύθυνση Η/Υ (host) και θύρα (port) όπου το πακέτο αποστέλλεται. +DatagramPacket(buf: byte[], length: int) +getdata(): byte[] +setdata(buf: byte[]): void Δημιουργεί ένα datagram από ένα byte array buf προσδιορισμένου length Επιστρέφει τα δεδομένα του πακέτου. Θέτει (τροποποιεί) τα δεδομένα του πακέτου DatagramSocket DatagramSoc ket Η κλάση DatagramSocket μια υποδοχή για αποστολή και λήψη datagram πακέτων. Μια υποδοχή datagram αποτελεί ένα σημείο αποστολής ή λήψης για μια υπηρεσία παράδοσης πακέτων. Κάθε πακέτο που στέλνεται ή λαμβάνεται μέσω μιας datagram socket δρομολογείται αυτόνομα. Όταν πολλαπλά λά πακέτα στέλνονται από έναν Η/Υ σε κάποιο άλλο, ενδέχεται να δρομολογηθούν από διαφορετικές διαδρομές και να φτάσουν με άλλη σειρά από τη σειρά στην οποία μεταδόθηκαν. Για να δημιουργήσετε μια DatagramSocket διακομιστή, Δημιιουργία DatagramSoc χρησιμοποιήστε τον κατασκευαστή DatagramSocket(int port), ket διακομιστή που συσχετίζει την υποδοχή με μια συγκεκριμένη θύρα στον τοπικό Η/Υ Δημιουργία DatagramSoc ket πελάτη Για να δημιουργήσετε μια DatagramSocket πελάτη, χρησιμοποιήστε τον κατασκευαστή DatagramSocket(), που συσχετίζει την υποδοχή με οποιαδήποτε διαθέσιμη θύρα στον τοπικό Η/Υ 14
Αποστολή και λήψη μέσω μιας DatagramSocket Αποστολή Λήψη Για την αποστολή δεδομένων, πρέπει να δημιουργηθεί ένα πακέτο, να οριστεί το περιεχόμενό του, να προσδιοριστεί η Internet διεύθυνση και αριθμός θύρας του παραλήπτη και να γίνει κλήση της μεθόδου send(packet) της κλάσης DatagramSocket Για τη λήψη δεδομένων, πρέπει να δημιουργηθεί ένα κενό πακέτο και να γίνει κλήση της ηςμεθόδου receive(packet) της κλάσης DatagramSocket Προγραμματισμός με Datagrams O προγραμματισμός με Datagrams διαφέρει από τον προγραμματισμό με stream sockets καθώς απουσιάζει η έννοια του ServerSocket στα datagrams. Ο διακομιστής και ο πελάτης αμφότεροι χρησιμοποιούν την κλάση DatagramSocket για να στείλουν και να λάβουν πακέτα DatagramServer DatagramSocket socket; socket = new DatagramSocket(8000); DatagramClient DatagramSocket socket; socket = new DatagramSocket(); byte[] buf = new byte[256]; DatagramPacket receivepacket = new DatagramPacket(buf, bef.length) socket.receive(receivepacket); get data from buf or receivepacket.getdata(); DatagramPacket sendpacket = new DatagramPacket(buf, bef.length) fill in the contents in buf; socket.send(sendpacket); byte[] buf = new byte[256]; InetAddress address = new InetAddress(serverName); DatagramPacket sendpacket = new DatagramPacket(buf, bef.length, address, 8000) fill in the contents in buf; socket.send(sendpacket); DatagramPacket receivepacket = new DatagramPacket(buf, bef.length) socket.receive(receivepacket); get data from buf or receivepacket.getdata(); 15
Άσκηση για το σπίτι: επικοινωνία πελάτηδιακομιστή με χρήση DatagramSocket Ξαναγράψτε το παράδειγμα επικοινωνίας πελάτη διακομιστή που αναλύθηκε προηγούμενα (με τον πελάτη να στέλνει στο διακομιστή την ακτίνα ενός κύκλου ή ένα αντικείμενο της κλάσης Circle κάνοντας χρήση socket streams), αυτή τη φορά χρησιμοποιώντας datagram sockets 16