Κατανεμημένα Συστήματα Java Sockets, Threads, Ασκήσεις Χρήστος Γκουμόπουλος Πανεπιστήμιο Αιγαίου Τμήμα Μηχανικών Πληροφοριακών και Επικοινωνιακών Συστημάτων
SOCKETS 2
Διαδιεργασιακή επικοινωνία με Sockets application layer Client Process Socket transport layer (TCP/UDP) network layer (IP) link layer (e.g. ethernet) physical layer ίκτυο ίκτυο application layer Server Process Socket transport layer (TCP/UDP) network layer (IP) link layer (e.g. ethernet) physical layer Διεπαφή (προγραμματιστική αφαίρεση) που παρέχει το ΛΣγια την πρόσβαση στο υποσύστημα επικοινωνίας (π.χ. UDP/TCP) Υλοποιούμε Ι/Ο στο δίκτυο μέσω τωνsockets περίπου όπως υλοποιούμε Ι/Ο στο δίσκο μέσω των περιγραφέων αρχείων.
Επικοινωνιακή σύνδεση Η επικοινωνιακή σύνδεση μεταξύ δύο διεργασιών περιγράφεται με μια πεντάδα της μορφής: (πρωτόκολλο, τοπική-διεύθυνση, τοπική-διεργασία, απομακρυσμένη-διεύθυνση, απομακρυσμένη-διεργασία). Το πρωτόκολλοαναφέρεται στο σύνολο των κανόνων που διέπουν την επικοινωνία (π.χ. UDP, TCP). Η τοπική-διεύθυνση και απομακρυσμένη-διεύθυνση, προσδιορίζουν τις IP διευθύνσεις των Η/Υ, στους οποίους εκτελούνται οι διεργασίες. Η τοπική-διεργασία και απομακρυσμένη-διεργασία, προσδιορίζουν την ταυτότητα των διεργασιών που θα επικοινωνούν (έναν 16-bit ακέραιο αριθμό, ο οποίος αναπαριστά την θύρα (port number) της διεργασίας) Το socket περιγράφει το ένα άκρο (endpoint)μιας επικοινωνιακής σύνδεσης. Περιλαμβάνει την τριάδα (πρωτόκολλο, διεύθυνση, αριθμό θύρας) για το ένα άκρο της επικοινωνιακής σύνδεσης. H διαδιεργασιακή επικοινωνία με sockets απαιτεί την μετάδοση μηνυμάτων μεταξύ ενός socket στη μια διεργασία και ενός socket στην άλλη διεργασία. 4
ΤCP/IP επικοινωνιακή σύνδεση Προσδιορισμός της διεύθυνσης της μηχανής στο δίκτυο IP address Ταυτότητα διεργασίας Port number To ζευγάρι (IP address, Port number) ορίζει ένα socketaddress Client socket address 195.251.177.9:23405 Server socket address 150.140.28.20:80 Client Connection socket pair (195.251.177.9:23405, 150.140.28.20:80) Server (port 80) Client host address 195.251.177.9 H θύρα 23405 είναι µια εφήµερη θύρα που έχει δοθεί από το ΛΣ Server host address 150.140.28.20 Η θύρα 80 είναι µια καλά καθορισµένη θύρα για πρόσβαση σε Web servers
Sockets στην Java Συλλογή από κλάσεις και interfaces στο πακέτο java.net Βασικές κλάσεις για TCP επικοινωνία: Socket: client-side& server-side ServerSocket: server-side Βασικές κλάσεις για UDP επικοινωνία : DatagramSocket DatagramPacket
Κλάση Socket (βασικές μέθοδοι) Socket(InetAddress address, int port) Δημιουργεί ένα TCP stream socket και το συνδέει στο port και την ΙΡ διεύθυνση που καθορίζονται ως παράμετροι. Socket(String host, int port) Δημιουργεί ένα TCP stream socket και το συνδέει στο port του host, του οποίου το όνομα καθορίζεται στις παραμέτρους. InputStream getinputstream() Επιστρέφει ένα stream εισόδου για αυτό το socket. OutputStream getoutputstream() Επιστρέφει ένα stream εξόδου αυτού του socket InetAddress getlocaladdress() Επιστρέφει την τοπική διεύθυνση με την οποία το socket αρχικοποιήθηκε. int getlocalport() Επιστρέφει τον τοπικό αριθμό θύρας με τον οποίο το socket αρχικοποιήθηκε. InetAddress getinetaddress() Επιστρέφει την διεύθυνση του απομακρυσμένου Η/Υ στην οποία το τοπικό socket είναι συνδεδεμένο. int getport() Επιστρέφει τον αριθμό θύρας της απομακρυσμένης διεργασίας στο socket της οποίας το τοπικό socket έχει συνδεθεί. void close() Κλείνει αυτό το socket (απελευθερώνει το socket description). 7
Κλάση ServerSocket(βασικές μέθοδοι) ServerSocket(intport) Δημιουργεί ένα serversocketστη θύρα της οποίας ο αριθμός καθορίζεται απ την παράμετρο. Αν ο αριθμός είναι το 0, τότε αφήνεται στο σύστημα η επιλογή μιας ελεύθερης (μη χρησιμοποιούμενης) θύρας. ServerSocket(intport, intbacklog) Δημιουργεί ένα serversocketστην θύρα της οποίας ο αριθμός καθορίζεται απ την πρώτη παράμετρο, ενώ επιτρέπει τόσες συνδέσεις να περιμένουν στην ουρά, μέχρι να γίνουν αποδεκτές, όσες η δεύτερη παράμετρος. Socketaccept() Ακούει για πιθανή αίτηση σύνδεσης ενός πελάτη με τον server και την αποδέχεται. Ένα νέο socket αντικείμενο δημιουργείται και χρησιμοποιείται για την ανταλλαγή μηνυμάτων με τον πελάτη. void close() Κλείνει αυτό το server socket. InetAddressgetInetAddress() Επιστρέφει την τοπική διεύθυνση αυτού του server socket. intgetlocalport() Επιστρέφει τον αριθμό της θύρας που αυτό το server socket«ακούει». 8
I/O μέσω του Stream μηχανισμού Stream = γενερικός μηχανισμός για Ι/Ο δεδομένων Input Stream = αντικείμενο που μια εφαρμογή μπορεί να χρησιμοποιήσει για να διαβάσει μια ακολουθία δεδομένων Output Stream: αντικείμενο που μια εφαρμογή μπορεί να χρησιμοποιήσει για να γράψει μια ακολουθία δεδομένων 9
Java Streams Συλλογή από stream κλάσεις και interfaces στο πακέτο java.io Στην κορυφή της ιεραρχίας ορίζονται δυο abstract κλάσεις: InputStream OutputStream 10
Διάφορες κλάσεις τύπου Stream Byte Array Streams ByteArrayInputStream & ByteArrayOutputStream File Access Streams FileInputStream & FileOutputStream Filter Streams (wrappers σε κλάσεις Streams): FilterInputStream & FilterOutputStream BufferedInputStream: BufferedOutputStream DataInputStream Μετατρέπει τα δεδομένα στον γηγενή τύπο τους καθώς τα λαμβάνει DataOutputStream Γράφει πρωτογενείς τύπους δεδομένων σε ένα output stream 11
Συνήθη βήματα ενός ΤCP client 12
Συνήθη βήματα ενός ΤCP server 13
Περίγραμμα εφαρμογής client-side server-side 14
Μετάδοση αντικειμένων (Serialization) Για την αποστολή/λήψη δεδομένων βασικών τύπων (integer, long, char, boolean, byte, float, double) μπορούν να χρησιμοποιηθούν οι αντίστοιχες μέθοδοι των κλάσεων DataInputStream/DataOutputStream(π.χ. writeint()/readint()) Για strings μπορούν να χρησιμοποιηθούν οι μέθοδοι writeutf()/readutf(). Για αντικείμενα κλάσεων, οι κλάσεις θα πρέπει να υλοποιούν το Serializable interfaceκαι θα πρέπει να χρησιμοποιηθούν μέθοδοι των κλάσεων ObjectInputStream/ObjectOutputStream writeobject() readobject() 15
Σχεδιασμός client/server εφαρμογών με Sockets Θα πρέπει να αποφασίσουμε την γλώσσα προγραμματισμού και το ΛΣ που θα χρησιμοποιήσουμε (πλατφόρμα ανάπτυξης) C, Java κ.ο.κ. UNIX, Linux, MS κ.ο.κ. Θα πρέπει να αποφασίσουμε εάν: θα υλοποιήσουμε την εφαρμογή πάνω από TCPή UDP Θα πρέπει να σχεδιάσουμε το πρωτόκολλο επικοινωνίας (request/reply protocol) Ορισμός της δομής των μηνυμάτων που ανταλλάσσονται To πρωτόκολλο μπορεί να είναι statefull ή stateless Θα πρέπει να υλοποιήσουμε την επιχειρησιακή λογική της εφαρμογής, την διεπαφή χρήστη,καθώς και την επικοινωνιακή σύνδεση χρησιμοποιώντας το API που μας παρέχει η πλατφόρμα ανάπτυξης που έχουμε επιλέξει Η ανταλλαγή μηνυμάτων θα πρέπει να λάβει υπόψη πιθανή ετερογένεια μεταξύ των συστημάτων που επικοινωνούν (π.χ., διαφορετική αναπαράσταση των βασικών τύπων δεδομένων) 16
THREADS 17
Διεργασίες Μια διεργασία(process) είναι ένα αυτοδύναμο περιβάλλον εκτέλεσης προγράμματος. Διαθέτει τους δικούς της αποκλειστικούς πόρους κατά το χρόνο εκτέλεσής της (με πιο σημαντικό αυτό της μνήμης). Μια εφαρμογή είναι δυνατό να αποτελείται από πολλές συνεργαζόμενες διεργασίες.
Νήματα Ένα νήμα(thread) είναι μια ακολουθιακή ροή ελέγχου (δηλαδή, κώδικας που έχει αρχή, περιέχει μια ακολουθία εντολών και έχει τέλος) σε ένα πρόγραμμα. Σκοπός είναι η απομόνωση (ή αυτονόμηση) κάποιων εργασιών (tasks), ώστε να μπορούν να εκτελούνται ταυτόχρονα(δυνητικά παράλληλα). Ένα νήμα δεν είναι από μόνο του ένα πρόγραμμα, αλλά ούτε διεργασία. Ένα νήμα εκτελείται στο εσωτερικό ενός προγράμματος. Ένα μόνο νήμα δεν προσφέρει ευκαιρίες πολυεπεξεργασίας. Δύο ή περισσότερα νήματα μπορούν να εκτελούνται ταυτόχρονα πραγματοποιώντας διαφορετικές εργασίες. 19
Πολυνηματικές εφαρμογές
Διεργασίες έναντι Νημάτων Οι διεργασίες : είναι ανεξάρτητες κουβαλούν σημαντική πληροφορία κατάστασης (process control block) διαθέτουν ξεχωριστό χώρο διευθύνσεων (address space) και αλληλεπιδρούν μόνον μέσω μηχανισμών διαδιεργασιακής επικοινωνίας που παρέχονται από το ΛΣ Τα νήματα: Μοιράζονται πληροφορίες κατάστασης μιας διεργασίας Μοιράζονται μνήμη και άλλους πόρους απ ευθείας Το context-switching μεταξύ νημάτων της ίδιας διεργασίας είναι τυπικά ταχύτερο από το context-switching μεταξύ διεργασιών τα νήματα είναι πιο αποδοτικά
Δημιουργία νημάτων στην Java Δύο τρόποι δημιουργίας νημάτων: Ως στιγμιότυπα υποκλάσεων της κλάσης java.lang.thread: + Πιο απλή προσέγγιση, κατάλληλη για απλές εφαρμογές. Τα νήματα περιορίζονται στο να είναι υποκλάσεις της συγκεκριμένης κλάσης. Ως στιγμιότυπα υλοποιήσεων της διεπαφής java.lang.runnable: +Η κλάση που υλοποιεί τη διεπαφή μπορεί να είναι υποκλάση οποιασδήποτε άλλης κλάσης. Και στις δύο περιπτώσεις πρέπει να οριστεί το σώμα της μεθόδου run, που είναι κενό και προσδιορίζει τι κάνει το νήμα. Η μέθοδος runείναι αφαίρεση μεθόδου της Runnable, αλλά και της Thread(καθώς η Thread υλοποιεί την Runnable). 22
Επεκτείνοντας την κλάσηthread Δημιουργία υποκλάσης της Thread public class ExampleThread extends Thread { } public void run() { } // perform whatever thread needs to do Για να ξεκινήσει ένα νέο thread ExampleThread et = new ExampleThread(); et.start(); Η start() ορίζεται στην Thread κλάση
Υλοποιώντας την διεπαφή Runnable Δημιουργία μιας κλάσης που υλοποιεί το Runnable interface public class ExampleRunnable implements Runnable { } public void run() { // perform whatever thread needs to do } Για να ξεκινήσει ένα νέο thread ExampleRunnable er = new ExampleRunnable(); new Thread(er).start();
Καταστάσεις νημάτων start() Ready to Run leaving non-runnable scheduling timeslice ends Running terminates Dead entering non-runnable Non-runnable states Waiting Sleeping Blocked
Μεταβάσεις από κλήση μεθόδων 26
Συγχρονισμός νημάτων Τα νήματα μοιράζονται τον ίδιο χώρο μνήμης κάθε νήμα διαθέτει το δικό του stack space, αλλά μοιράζονται το ίδιο heap (για διαχείριση δυναμικής μνήμης) Ο συγχρονισμός είναι απαραίτητος για : ακεραιότητα δεδομένων προσπέλαση κοινών πόρων 27
Monitor: μηχανισμός συγχρονισμού Μια περιοχή του κώδικα που αναπαριστά έναν κοινό πόρο (κρίσιμη περιοχή) μπορεί να συσχετισθεί με έναmonitor(αλλιώς semaphore). Τα νήματα κάνουν προσπέλαση του κοινού πόρου κλειδώνοντας πρώτα το monitor Μόνον ένανήμα μπορεί να κατέχει το monitor σε μια δεδομένη χρονική στιγμή Τα monitors υλοποιούν έναν μηχανισμό αμοιβαίου αποκλεισμού (αλλιώς mutex)
Συνθήκες ανταγωνισμού (race conditions) Συνθήκη ανταγωνισμού(race condition)έχουμε όταν δύο νήματα προσπαθούν να προσπελάσουν το ίδιο αντικείμενο ταυτόχρονα και η συμπεριφορά του προγράμματος δεν εξαρτάται πλέον μόνο από τις εντολές του, αλλά και από το ποιο νήμα προηγείται στη σειρά προσπέλασης (δεν είναι προβλέψιμο αφού αυτό εξαρτάται από το λειτουργικό σύστημα). Αν όλα τα νήματα εκτελούν αποκλειστικά λειτουργίες ανάγνωσης, τότε δεν υπάρχει κίνδυνος συνθήκης ανταγωνισμού. Στην περίπτωση συνθήκης ανταγωνισμού τα αποτελέσματα των υπολογισμών δεν είναι συνεπή σε διαδοχικές επαναλήψεις εκτέλεσης του προγράμματος με την ίδια είσοδο.
Συνθήκες ανταγωνισμού (race conditions) Πρέπει να εξασφαλιστεί ότι το ένα νήμα δεν θα αλλάξει τα δεδομένα αυτά, ενώ τα διαχειρίζεται κάποιο άλλο νήμα αναγκαίος ο συγχρονισμός νημάτων. Για το λόγο αυτό, η Java επιτρέπει το κλείδωμα αντικειμένων και μεθόδων με τη χρήση της δεσμευμένης λέξης synchronized.
Συγχρονισμός νημάτων Συγχρονισμός νημάτων επιτρέποντας την προσπέλαση μιας μεθόδου (ή ενός αντικειμένου) σε ένα μόνο νήμα κάθε φορά: 31 public synchronized void xmethod { } Για να προσπελάσει ένα νήμα τη μέθοδο xmethodπρέπει να περιμένει να τελειώσει το τρέχον νήμα (blocked). Μπορεί να δηλωθεί ως συγχρονισμένο ένα τμήμα κώδικα στο σώμα μιας μεθόδου (και όχι όλη η μέθοδος): public int ymethod { }... synchronized (this) {... }
ΑΣΚΗΣΕΙΣ 32
Άσκηση : Echo serviceμε Java Sockets Θέλουμε να αναπτύξουμε μια κατανεμημένη εφαρμογή τύπου client/server που υλοποιεί μια echo υπηρεσία πάνω από Java TCP streams. Χρειαζόμαστε δύο προγράμματα: TCPClientκαι TCPServer. Mπορείτε με την javac εντολή σε ένα command line παράθυρο να μεταγλωττίσετε τον κώδικα του client (TCPClient.java) και τον κώδικα του server (TCPServer.java). Στην συνέχεια σε δύο ξεχωριστά παράθυρα τρέξτε πρώτα τον server και έπειτα τον client.ενδεικτικά οι εντολές που θα πρέπει να δώσετε είναι: java TCPServer java TCPClient Hello MPES Dept localhost Στη συνέχεια τροποποιείστε τον κώδικα του server ώστε να τυπώνει την IP διεύθυνση και το port number του Η/Υ από τον οποίο έλαβε το αίτημα. 33
TCPClient TCP client makes connection to server, sends request and receives reply import java.net.*; import java.io.*; public class TCPClient { public static void main (String args[]) { // arguments supply message and hostname of destination Socket s = null; try{ int serverport = 7896; s = new Socket(args[1], serverport); DataInputStream in = new DataInputStream( s.getinputstream()); DataOutputStream out = new DataOutputStream( s.getoutputstream()); out.writeutf(args[0]); // UTF is a string encoding String data = in.readutf(); System.out.println("Received: "+ data) ; }catch (UnknownHostException e){ System.out.println("Sock:"+e.getMessage()); }catch (EOFException e){system.out.println("eof:"+e.getmessage()); }catch (IOException e){system.out.println("io:"+e.getmessage());} }finally {if(s!=null) try {s.close();}catch (IOException e) {System.out.println("close:"+e.getMessage());}} } }
TCPServer TCP server makes a connection for each client and then echoes the client s request import java.net.*; import java.io.*; public class TCPServer { public static void main (String args[]) { try{ int serverport = 7896; ServerSocket listensocket = new ServerSocket(serverPort); while(true) { Socket clientsocket = listensocket.accept(); Connection c = new Connection(clientSocket); } } catch(ioexception e) {System.out.println("Listen :"+e.getmessage());} } } // this code continues on the next slide
TCPServer(συνέχεια) class Connection extends Thread { DataInputStream in; DataOutputStream out; Socket clientsocket; public Connection (Socket aclientsocket) { try { clientsocket = aclientsocket; in = new DataInputStream( clientsocket.getinputstream()); out =new DataOutputStream( clientsocket.getoutputstream()); this.start(); } catch(ioexception e) {System.out.println("Connection:"+e.getMessage());} } } public void run(){ try { } // an echo server String data = in.readutf(); out.writeutf(data); } catch(eofexception e) {System.out.println("EOF:"+e.getMessage()); } catch(ioexception e) {System.out.println("IO:"+e.getMessage());} } finally{ try {clientsocket.close();}catch (IOException e){/*close failed*/}}
Επέκταση της κλάσης TCPServer public class TCPServer { public static void main (String args[]) { try{ int serverport = 7896; // the server port ServerSocket listensocket = new ServerSocket(serverPort); while(true) { Socket clientsocket = listensocket.accept(); } } } Connection c = new Connection(clientSocket); } catch(ioexception e) {System.out.println("Listen socket:"+e.getmessage());} 37
Επέκταση της κλάσης TCPServer public class TCPServer { } public static void main (String args[]) { } try{ int serverport = 7896; // the server port ServerSocket listensocket = new ServerSocket(serverPort); while(true) { Socket clientsocket = listensocket.accept(); System.out.println("Connection request form client: " + clientsocket.getinetaddress() + " : " + clientsocket.getport()); } Connection c = new Connection(clientSocket); } catch(ioexception e) {System.out.println("Listen socket:"+e.getmessage());} 38
Άσκηση σε συγχρονισμό Για το σενάριο που θα περιγράψουμε θα θεωρήσουμε ότι ο ίδιος τραπεζικός λογαριασμός μπορεί να προσπελασθεί από πολλούς πελάτες (κοινός λογαριασμός). Για κάθε αίτημα που φθάνει στον εξυπηρετητή δημιουργείται ένα διαφορετικό νήμα που τρέχει τον κώδικα εξυπηρέτησης. Ας υποθέσουμε ότι για την διαχείριση της ανάληψης ενός ποσού (amount) από έναν λογαριασμό (που αναγνωρίζεται με το PIN mypin) χρησιμοποιείται ο παρακάτω κώδικας που τρέχει εντός του κάθε νήματος (thread)που εξυπηρετεί έναν πελάτη. Υποθέτουμε μονοεπεξεργαστικό σύστημα στην πλευρά του εξυπηρετητή. 1. case CMD_WITHDRAW: 2. Account sessionacc = getsessionaccount(mypin); // τοπικό αντίγραφο Account 3. String result = sessionacc.withdraw(amount); // ανάληψη του ποσού 4. savesessionaccount(sessionacc); // αποθήκευση τοπικού αντιγράφου 5. break; 39
Άσκηση σε συγχρονισμό Toσενάριο έχει ως εξής: δυο πελάτες (Π1 και Π2) διαθέτουν έναν κοινό λογαριασμό (Λ) και αφού έχουν ελέγξει το υπόλοιπο του λογαριασμού, το οποίο είναι 5000, στην συνέχεια προσπαθούν ταυτόχρονα να κάνουν ανάληψη ποσού 3000 ο πρώτος και 4000 ο δεύτερος. Περιγράψτε ένα σενάριο που οδηγεί σε συνθήκες ανταγωνισμού. Συνθήκη ανταγωνισμού (race condition) έχουμε όταν δύο νήματα προσπαθούν να προσπελάσουν το ίδιο αντικείμενο ταυτόχρονα και η συμπεριφορά του προγράμματος δεν εξαρτάται πλέον μόνο από τις εντολές του, αλλά και από το ποιο νήμα προηγείται στη σειρά προσπέλασης. Toπεδίο εφαρμογής της άσκησης ευνοεί την εμφάνιση καταστάσεων ανταγωνισμού και δημιουργεί αντίστοιχες απαιτήσεις για την αντιμετώπισή τους. 40
Knock Kncok Who s there? Θέλουμε να υλοποιήσουμε το παιδικό παιχνίδι αστείου λόγου Knock Kncok Who s there ως μια κατανεμημένη εφαρμογή τύπου πελάτης-εξυπηρετητής με Java sockets. To παιχνίδι περιλαμβάνει τον αστειολόγο (ο εξυπηρετητής) και τον παραλήπτη του λογοπαιγνίου (πελάτης). Η αλληλεπίδρασή τους περιλαμβάνει τα ακόλουθα βήματα: Αστειολόγος : λέει Knock, knock! Παραλήπτης: πρέπει να πει Who's there? Αστειολόγος : λέει ένα όνομα (π.χ. Atch) Παραλήπτης: πρέπει να επαναλάβει το όνομα με το ερώτημα ποιος; (π.χ. Atch who?) Αστειολόγος: λέει το λογοπαίγνιο (π.χ. Bless you!) 41
Προδιαγραφές KKClient Για την εφαρμογή μας η τελευταία απάντηση (λογοπαίγνιο) του εξυπηρετητή συνοδεύεται από το ερώτημα προς τον παραλήπτη αν θέλει να συνεχίσει το παιχνίδι. Αν η απάντηση είναι «ναι» συνεχίζει ένας νέος κύκλος, ενώ αν η απάντηση είναι «όχι», ο εξυπηρετητής στέλνει μήνυμα Bye. και το πρόγραμμα του πελάτη τερματίζει. Ο εξυπηρετητής εξακολουθεί να εκτελείται. Το πρόγραμμα του πελάτη υλοποιείται από μια κλάση, έστω ΚΚClient. Η κλάση ΚΚClient πραγματοποιεί την σύνδεση με τον εξυπηρετητή (για την σύνδεση χρησιμοποιείστε το ζευγάρι {localhost, 5050})και υλοποιεί τον ψευδοκώδικα που σας δίνεται παρακάτω. 42
Προδιαγραφές KKClient 43
Προδιαγραφές KKServer 44
Προδιαγραφές KKProtocol 45
Concurrent Server 46