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