non bloccante - Università degli Studi di Milano
Transcript
non bloccante - Università degli Studi di Milano
Implementazione di un Server Concorrente Corso di laurea in Informatica Laboratorio di Reti di Calcolatori A.A. 2013-2014 Simone Bassis [email protected] Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. Server multithread o Il server principale gestisce sia le chiamate che lo scambio dati o Lo fa in maniera concorrente o Non c’è più il concetto di coda di attesa o Gli stessi client possono adottare una politica analoga Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 260 1 Lo schema completo Creazione Socket Creazione Socket Invio/Ricezione ‘evento’ Invio/Ricezione ‘evento’ Stipulazione connessione Richiesta connessione Scambio dati Scambio dati Chiusura canale dati Chiusura canale dati Server Client Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 261 I/O non bloccante o Chi blocca cosa? o Read bloccante: la lettura non restituisce il controllo del flusso fintantochè o esistano dei dati nel buffer o o non sia stata raggiunta la quantità di dati richiesti o Read non bloccante o termina imprevedibilmente quando sono stati letti da zero a dim_buffer byte • Ma la imprevedibilità è solo finta Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 262 2 Architettura • Rappresentazione del canale nel selettore • Servono al selettore per sortare/gestire le richieste • Ogni chiave rappresenta una singola sottorichiesta del client • Contiene informazioni per identificare client e tipo di richiesta Canale di comunicazione: i dati passano sottoforma di Buffer java.nio Una chiave non rappresenta l’intero stream che il client manda al server • Monitora i canali registrati • Serializza le richieste che il server deve processare • Divide i dati in sottorichieste identificate da opportune chiavi Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 263 I Buffer o o È l’unico modo in cui si trasferiscono dati su un socket channel Un contenitore limitato di dati primitivi o Al contrario di Vector, ArrayList o Può contenere solo tipi base (int, char, …) • Quelli con l’iniziale minuscola, per intenderci o Si tratta di una classe astratta o Con 7 implementazioni • • • • • • • ByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer LongBuffer ShortBuffer Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 264 3 Buffer: cosa basta sapere o Come allocarli • Cerca di usare le operazioni di I/O native dell’OS • La sua implementazione dipende dalla JVM ByteBuffer buffer1 = ByteBuffer.allocate(1024); ByteBuffer buffer2 = ByteBuffer.allocateDirect(1024); ByteBuffer buffer3 = ByteBuffer.wrap(new String("hello").getBytes()); o Quali proprietà o o o o Capacity: numero degli elementi che contiene. Limit: indice del primo elemento che non deve essere letto o scritto Position: indice del prossimo elemento da leggere/scrivere Mark: è l’indice a cui la posizione sarà settata quando il metodo reset viene invocato. 0 ≤ mark ≤ position ≤ limit ≤ capacity o Come accedervi o In generale tramite metodi get e put o La loro tipologia dipende dall’implementazione • (ByteBuffer, CharBuffer, …) Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 265 I/O con Buffer o Per scrivere IntBuffer buffer = IntBuffer.allocate(10); for (int i=0; i < buffer.capacity(); i++) { buffer.put(i); } o Per leggere buffer.position(0); while (buffer.hasRemaining()) { int i = buffer.get(); System.out.println("i="+i); } Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 266 4 Occhio a flip, clear e rewind o Flip() o Rende il buffer pronto per una sequenza di • • Scritture su canale Operazioni get o Setta limit alla posizione corrente o Setta la posizione corrente a 0 o Clear() o Rende il buffer pronto per una sequenza di • • Letture da canale Operazioni put o Setta la posizione corrente a 0 o Setta limit alla capacity del buffer • Annulla cioè l’effetto di una flip o buffer.position(5); buffer.flip(); while (buffer.hasRemaining()) { int i = buffer.get(); System.out.println("i="+i); } buffer.clear(); while (buffer.hasRemaining()) { int i = buffer.get(); System.out.println("i="+i); } Rewind() o Rende il buffer pronto per rileggere i dati che contiene o Lascia limit inalterato o Setta la posizione corrente a 0 Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 267 Il Selector o Può monitorare più SelectableChannel o Per quanto ci riguarda, più connessioni o Informa l’applicazione quando è il momento di processare la richiesta o Quando qualcosa di interessante transita sul canale o Lo fa creando delle chiavi o istanze di SelectionKey Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 268 5 La SelectionKey o o E’ un rappresentante del canale nel selettore Ogni chiave è composta da o Canale: può essere solo non bloccante o Operazione: il tipo (la natura) della richiesta • Cui corrisponde un tipo di chiave • Connectable – Il client ha richiesto una connessione • Acceptable – Il server ha accettato la connessione • Readable – Il server può leggere • Writeable – Il server può scrivere Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 269 La SelectionKey o Allegato: un riferimento di tipo Object • Semplifica l’accesso alle informazioni sullo stato pregresso delle operazioni sul canale – Nessuna operazione è atomica in questo contesto • In soldoni: L'allegato consente di tenere traccia di quanto è stato fatto affinchè due operazioni successive della stessa specie possano essere accumulate in vista di un risultato comune • Da JavaDoc: “It is often necessary to associate some application-specific data with a selection key, for example an object that represents the state of a higher-level protocol and handles readiness notifications in order to implement that protocol.” Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 270 6 Server non bloccante pseudo algoritmo crea SocketChannel; crea Selector associa il SocketChannel al Selector for(;;) { attendi il verificarsi di eventi dal Selector; evento verificato; recupera le chiavi; per ogni chiave creata dal Selector { verifica il tipo di richiesta; isAcceptable: ottieni il SocketChannel del client; associa quel SocketChannel al Selector; registralo per operazioni di lettura/scrittura continue; isReadable: ottieni il SocketChannel del client; leggi dalla socket; continue; isWriteable: ottieni il SocketChannel del client; scrivi sulla socket; continue; } Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 } S. Bassis ([email protected]) Università di Milano – D.I. 271 E qualche sana istruzione Java // Crea il socket channel e configuralo come non bloccante ServerSocketChannel server = ServerSocketChannel.open(); server.configureBlocking(false); server.socket().bind(new java.net.InetSocketAddress(host,2001)); System.out.println("Server attivo porta 2001"); // Crea il selettore e registra il server channel al Selector Selector selector = Selector.open(); server.register(selector,SelectionKey.OP_ACCEPT, null); L’eventuale allegato Tipo di registrazione Significato: il Selector riporta che … OP_ACCEPT Il client richiede una connessione al server OP_CONNECT Il server ha accettato la richiesta di connessione OP_READ Il channel contiene dati da leggere OP_WRITE Il channel contiene dati da scrivere Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 272 7 Selector e.. select o Finchè il selettore è attivo... while(selector.isOpen()) { o ...chiediamo al selettore quelle chiavi, o tra quelle presenti nel suo registro, o che siano pronte ad eseguire l'operazione o per cui abbiamo manifestato interesse. Selector.select() Selector.selectNow() Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. // Bloccante // Non Bloccante • Meglio aggiungere un Thread.sleep() per salvaguardare la CPU 273 Il loop infinito (parte I) • Blocca l’esecuzione • Attende eventi registrati sul Selector // Loop infinito lato server for(;;) { // Attendi un evento e ottieni la chiave selector.select(); Set keys = selector.selectedKeys(); • Le chiavi possono (anzi Iterator i = keys.iterator(); devono) essere rimosse • Le chiavi usate vanno rimosse • Non possono mai essere // Per ogni chiave... aggiunte while(i.hasNext()) { SelectionKey key = (SelectionKey) i.next(); i.remove(); • Attenzione: anche questa • Come per le socket tradizionali ma… • Vanno settate come non bloccanti • Vanno registrate al Selector accept non è bloccante if (key.isAcceptable()) { // connessione accettata SocketChannel client = server.accept(); if (client != null) { client.configureBlocking(false); client.register(selector, SelectionKey.OP_READ); } continue; } Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 274 8 Il loop infinito (parte II) if (key.isReadable()) { SocketChannel client = (SocketChannel) key.channel(); // Lettura tramite ByteBuffer int BUFFER_SIZE = 32; ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); try { • I dati transitano solo su client.read(buffer); ByteBuffer } catch (Exception e) { e.printStackTrace(); continue; • Non è detto che tutti I } dati siano arrivati • Ritorna il canale per cui è stata creata questa socket • Non si cicla fino a // Mostra i caratteri a console riceverli tutti buffer.flip(); Charset charset=Charset.forName("ISO-8859-1"); CharsetDecoder decoder = charset.newDecoder(); CharBuffer charBuffer = decoder.decode(buffer); System.out.print(charBuffer.toString()); continue; • La gestione dei Buffer è diversa da quella degli stream • Ma è molto più flessibile } } } Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 275 E il client non bloccante? Invia al server in un ciclo infinito la stringa Client ID // Come per il server SocketChannel client = SocketChannel.open(); client.configureBlocking(false); client.connect(new java.net.InetSocketAddress(host,2001)); • Il Selector attende che il server accetti la connessione Selector selector = Selector.open(); SelectionKey clientKey = client.register(selector, SelectionKey.OP_CONNECT); • Attesa limitata a 500 ms • Timeout: server non attivo // In attesa della connessione while (selector.select(500)> 0) { // Ottieni le chiavi Set keys = selector.selectedKeys(); Iterator i = keys.iterator(); • Vero se è iniziata sul canale while (i.hasNext()) { una op. di connessione non SelectionKey key = (SelectionKey)i.next(); ancora terminata i.remove(); SocketChannel channel = (SocketChannel)key.channel(); if (key.isConnectable()) { System.out.println("Server Found"); if (channel.isConnectionPending()) channel.finishConnect(); Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. • Attende che il processo di connessione termini, se bloccante • Ritorna true o false informando sullo stato della connessione se non bloccante • Sarebbe buona norma deregistrare la key a connessione 276 avvenuta, onde evitare ulteriori richiami 9 Client: scambio dati // Invia continuamente al server la stringa Client id ByteBuffer buffer = null; for (;;) { buffer = ByteBuffer.wrap( new String(" Client " + id + " ").getBytes()); channel.write(buffer); buffer.clear(); } } } } Prestate attenzione: o Tutte le op. su un canale non bloccante sono non bloccanti o Le chiavi non sono selezionate in base alle operazioni per cui sono state registrate (InterestOps) ma in base alle operazioni che il loro canale poteva fare al momento della selezione (ReadyOps). Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 277 Scrittura per canali non bloccanti o Buona norma avere un protocollo che preveda come prefisso la dim. attuale del messaggio private ByteBuffer packMessage(String text) { ByteBuffer data = (ByteBuffer)Charset.forName("UTF-8").encode(text); int length = data.remaining(); ByteBuffer pack = ByteBuffer.allocate(length + 4); pack.putInt(length).put(data).rewind(); • Ritorna il numero di return pack; byte tra la posizione } corrente e il limite ByteBuffer message = packMessage("Hello world!"); o Per mantenere informazioni aggiuntive protocol dependent, usate if(connected) { client.register(selector, OP_READ | OP_WRITE, message); l’allegato } else { client.register(selector, OP_CONNECT, message); } … • Un canale è sempre ByteBuffer data = (ByteBuffer)key.attachment(); • Vero se ci sono dati OP_WRITE finchè è SocketChannel out = (SocketChannel)key.channel(); tra la posizione aperto if(data.hasRemaining() && out.finishConnect()) { corrente e il limite out.write(data); Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 278 S. Bassis ([email protected]) } Università di Milano – D.I. 10 Lettura per canali non bloccanti o Convenzione : quando una socket riceve un messaggio i primi quattro byte che riceve sono i costituenti di un intero che rappresenta il numero ulteriore di byte che devono essere letti affinchè si possa dire di avere un messaggio completo o Potrebbero servire i metodi: o public void accumulate(ByteBuffer data) { ... } per accumulare i dati letti dal SocketChannel, intepretando i primi quattro byte come intero o public boolean messageComplete() { ... } per sapere se il messaggio possa dirsi completato o public String decodeMessage() { ... } per ottenere, al termine, il testo ricevuto Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 279 Una possibile implementazione (1) import java.nio.*; import java.nio.charset.*; public class BufferedMessage { private ByteBuffer sizeBuffer = ByteBuffer.allocate(4); private boolean sizeRead = false; private ByteBuffer messageBuffer; public void accumulate(ByteBuffer data) { while(!sizeRead && data.hasRemaining()) { sizeBuffer.put(data.get()); if(sizeBuffer.hasRemaining() == false) { sizeBuffer.rewind(); int messageSize = sizeBuffer.getInt(); messageBuffer = ByteBuffer.allocate(messageSize); sizeRead = true; } } if(data.hasRemaining()) { messageBuffer.put(data); } } Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 280 11 Una possibile implementazione (2) public void reset() { messageBuffer = null; sizeBuffer.rewind(); sizeRead = false; } public boolean messageComplete() { return messageBuffer != null && messageBuffer.hasRemaining() == false; } public String decodeMessageAndReset() { messageBuffer.flip(); String message = Charset.forName("utf-8").decode(messageBuffer).toString(); messageBuffer.reset(); return message; } } Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 281 …e la lettura vera e propria if(key.isReadable()) { SocketChannel in = (SocketChannel)key.channel(); BufferedMessage message = (BufferedMessage)key.attachment(); ByteBuffer data = ByteBuffer.allocate(64); try { in.read(data); } catch(IOException ex) { key.cancel(); } • Cancella la chiave di selezione • Valida al prossimo select() if(key.isValid()) { data.flip(); buffer.accumulate(data); if(buffer.messageComplete()) { System.out.println(buffer.decodeMessageAndReset()); } } } Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 282 12 Non c’è limite alla fantasia… Multiple Reactor with Thread Pools Architecture Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 283 … ma i problemi non mancano o Non tutti i channel sono selectable o ServerSocketChannel, SocketChannel o Pipe.SinkChannel, Pipe.SourceChannel • Connessione one-way tra due thread o DatagramChannel o Non è possibile associare SelectableChannel a o File o System.in, System.out ReadableByteChannel in = Channels.newChannel(System.in); FileChannel fc = new FileOutputStream("data.txt").getChannel(); Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 284 13 Alcune soluzioni… non standard Selector selector = Selector.open(); SystemInPipe stdinPipe = new SystemInPipe(); SelectableChannel stdin = stdinPipe.getStdinChannel(); ByteBuffer buffer = ByteBuffer.allocate (32); stdin.register (selector, SelectionKey.OP_READ); stdinPipe.start(); • In realtà si basa su un approccio multithread o • Classe che incapsula System.in in un SelectableChannel La classe SystemInPipe è disponibile insieme ad altri programmi di utility su nio al link http://javanio.info/filearea/bookexamples/unpacked/com/ronsoft/books/nio/channels/SystemInPipe.java o In soldoni: o Al momento l’approccio concorrente è valido principalmente per applicazioni server o A nessuno interessa associarlo a letture/scritture da/su file o altri stream Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 285 Esercizio 24 Si riformuli l’Esercizio 22 in modo tale che gli attori in gioco agiscano tramite I/O non bloccante e senza l’ausilio di thread multipli, usado le primitive della programmazione concorrente Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 286 14 Esercizio 24 (un po’ difficile) Si riformuli la chatroom dell’Esercizio 23 tramite I/O non bloccante e senza l’ausilio di thread multipli, usado le primitive della programmazione concorrente. Si può pensare di usare la classe SystemInPipe disponibile al link http://javanio.info/filearea/bookexamples/unpacked/co m/ronsoft/books/nio/channels/SystemInPipe.java per ottenere un SelectableChannel da System.in Laboratorio di Reti di Calcolatori (Informatica) - A.A. 2013-2014 S. Bassis ([email protected]) Università di Milano – D.I. 287 15
Documenti analoghi
Presentazione del corso - Università degli Studi di Milano
Esercizio C1
Scrivere un programma il più possibile simile al
programma nslookup, disponibile sia in
ambiente unix che windows, cercando di
implementarne le sole caratteristiche di base:
Presentazione del corso - Università degli Studi di Milano
Degli elementi per identificare il canale