Java IP Sorties
Java IP Sorties
Depuis SDK 1.4, [Link].* NIO (New Input Output) Utilisés par les primitives d'entrées-sorties de
Gestion plus fine de la mémoire [Link]
Gestion plus performante des entrées-sorties Remplace les tableaux utilisés en [Link]
Gestion simplifiée des différents jeux de caractères Zone de mémoire contiguë, permettant de stocker
Interaction plus fine avec le système de fichiers
une quantité de données fixée,
Utilisation d'entrées-sorties non bloquantes (plus d'un type primitif donné
tard) A priori pas prévus pour accès concurrent
Nouveaux concepts dans les entrées-sorties en
il faudra les protéger en cas de besoin
Java
Représentés par des classes abstraites
Buffers (tampons mémoire) [Link].* Permet de dédier l'implémentation native à la plate-
Charsets (jeux de caractères) forme d'accueil
[Link].*
Channels (canaux) [Link].*
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 1 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 2
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 3 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 4
Accès aléatoire vs séquentiel Les attributs et méthodes d'un tampon
Si PrimBuffer est un buffer d'éléments de type Capacité: nombre d'éléments qui peuvent être
prim : contenus
Accès aléatoire (absolute) fixée à la création du tampon
prim get(int index) donne l'élément à la position consultable par int capacity()
index Limite: indice du premier élément ne devant pas
primBuffer put(int index, prim value) ajoute value à être atteint
l'indice index, et renvoie le buffer modifié par défaut, égale à la capacité.
comme [Link]() Fixée par Buffer limit(int newLimit)
Peuvent lever IndexOutOfBoundsException Connue par int limit()
Accès séquentiel (relative)
Position courante: indice du prochain élément
Prim get() resp. primBuffer put(prim value) accessible
Donne la valeur (resp. place value) à la position Consultable: int position()
courante Modifiable: Buffer position(int newPosition)
Peuvent lever des exceptions
BufferUnderflowException ou
BufferOverflowException
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 5 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 6
Les attributs et méthodes d'un tampon Les attributs et méthodes d'un tampon
Marque (éventuelle): position dans le tampon Quand la position courante vaut la limite
Buffer mark() place la marque à la position courante Un appel à get() provoque BufferUnderflowException
Buffer reset() place la position à la marque Un appel à put() provoque BufferOverflowException
ou lève InvalidMarkException
Pour éviter ça:
La marque est toujours inférieure à la position. int remaining() donne le nombre d'éléments entre la
Si la position ou la limite deviennent plus petite que la position courante et la limite
marque, la marque est effacée boolean hasRemaining() vaut vrai si la position est
Invariant: strictement inférieure à limite
0 <= marque <= position <= limite <=
capacité
Buffer rewind()
met la position à 0 et supprime la marque
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 7 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 8
Les attributs et méthodes "en gros" Exemple de tampon aléatoire
Les méthodes existent en version "en gros" (bulk) final static int SIZE=32;
Manipulent un tableau au lieu d'une variable public static void main(String[] args) {
primBuffer get(prim[] dest, int offset, int length) et // Accès aléatoire (mode tableau)
primBuffer get(prim[] dest) IntBuffer iba = [Link](SIZE);
for (int i=0; i<SIZE; i++) {
Tentent de lire le nombre d'éléments spécifié, ou
[Link](i,2*i); // Met la valeur 2*i à l'indice i
rien si pas assez de choses à lire }
(BufferUnderflowException)
primBuffer put(prim[] src, int offset, int length) et for (int i=0; i<SIZE; i++) {
primBuffer put(prim[] src) [Link]([Link](i));
Tentent d'écrire le nombre d'éléments spécifié, } // affiche: 0 2 4 ... 62
ou rien si pas assez de place
[Link]([Link](2)); // affiche 4
(BufferOverflowException)
[Link]([Link](31)); // affiche 62
primBuffer put(primBuffer src) [Link]([Link](32));
Tente d'écrire le contenu de src // lève IndexOutOfBoundsException
(BufferOverflowException) }
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 9 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 10
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 13 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 14
ByteBuffer bb1 = [Link](10); char[] t1 = {'a', 'b', 'c', 'd', 'e', 'f'};
ByteBuffer bb2 = [Link](); // 2 accès possibles CharBuffer cb1 = [Link]([Link]);
for (int i=0; i<[Link](); i++){ [Link](t1); // met le contenu de t1 dans cb1
[Link]((byte)i); // remplissage par bb1 [Link](2); // place cb1 prêt à lire 'c'
} [Link](5); // place limite de cb1 après 'e'
[Link]([Link]()); // affiche 10 [Link]([Link]()); // affiche 3
[Link]([Link]()); // affiche 0
[Link]((byte)3); // déplace la position de b2 en 1 // Crée un tampon partagé pour ce qui reste dans cb1
[Link]([Link](0)); // affiche 3 CharBuffer cb2 = [Link]();
[Link]([Link]()); // affiche 10 [Link]([Link]()); // affiche 3
[Link]('x'); // met 'x' en 0 dans cb2 (remplace 'c')
ByteBuffer bb3 = [Link](); for ([Link](); [Link](); )
[Link]([Link]()); // affiche 1 [Link]([Link]()); // affiche a b x d e
[Link](); // place la position de b3 en 0
[Link]([Link]()); // affiche 3 [Link](4,'y');// met 'y' en 4 dans cb1 (remplace 'e')
[Link]((byte)4); for ([Link](); [Link](); )
// throws [Link] [Link]([Link]()); // affiche x d y
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 15 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 16
Tampons directs et non directs Tampon comme enveloppe de tableau
Par défaut, allocate() renvoit un tampon non- Méthode statique wrap() dans chaque classe de
direct tampon (pour chaque type primitif) pour
Allocation classique dans le tas "garbage collecté" envelopper un tableau
[Link]() renvoit un tampon primBuffer wrap(prim[] tab, int offset, int length) ou
direct primBuffer wrap(prim[] tab)
La mémoire associée peut être réservée en dehors Enveloppe la totalité du tableau: capacité vaut
du tas classique (mémoire interne de la JVM) afin [Link]
d'optimiser les opérations de lecture et d'écriture Position du tampon produit est mise à offset (sinon 0)
natives (éviter de recopier les buffers) Limite est mise à offset+length (sinon [Link])
Coûteux en allocation et déallocation Si un tampon est une enveloppe de tableau
À réserver pour les tampons d'entrées-sorties de
hasArray() retourne true
taille et de durée de vie importantes
array() retourne le tableau
Une "vue" d'un tampon d'octets direct est directe arrayOffset() retourne le décalage du tampon par
Peut être testé par isDirect() rapport au tableau
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 17 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 18
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 19 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 20
Comparaisons de tampons Types primitifs et représentation
Les classes des tampons implémentent l'interface Chaque classe de tampon de prim (type primitif)
Comparable<tamponDeMemeType> permet de lire (get) et d'écrire (put) des éléments de
Comparable uniquement avec un tampon du même
type prim
type La classe ByteBuffer sait en plus lire et écrire
compareTo() compare les séquences d'éléments
restants (au sens de remaining()) de manière
n'importe quel type primitif avec getPrim() ou
lexicographique (le prochain, puis le suivant, etc.) putPrim()
Nécessité de choisir l'ordre de représentation des types
Deux tampons sont égaux au sens de equals() si
primitifs (ordre de stockage des octets)
1. ils ont le même type d'éléments,
ByteOrder.BIG_ENDIAN (gros-boutiste): octet de poids
2. ils ont le même nombre d'éléments restants
fort stocké à l'indice le plus petit
et
ByteOrder.LITTLE_ENDIAN (petit-boutiste): octet de
3. les deux séquences d'éléments restants,
poids faible stocké à l'indice le plus petit
considérées indépendament de leurs positions
de départ, sont égales élément par élément.
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 21 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 22
Ordre de représentation des tampons Exemple sur les ordres des tampons
[Link]() donne l'ordre de [Link]([Link]()); // LITTLE_ENDIAN
[Link]([Link](134480385));
stockage natif de la plateforme // affiche 00001000 00000100 00000010 00000001 ie {8,4,2,1}
L'ordre d'un ByteBuffer peut être consulté ou fixé ByteBuffer bb = [Link](8);
[Link]([Link]()); // BIG_ENDIAN
par order() // On écrit les 4 octets en big endian (8 en 0, 4 en 1...)
Par défaut, tout tampon d'octet (ByteBuffer) [Link]((byte)8).put((byte)4).put((byte)2).put((byte)1);
// Puis les 4 octets dans l'ordre little endian
alloué est en big endian [Link]((byte)1).put((byte)2).put((byte)4).put((byte)8);
Tous les autres tampons sont créés avec // On prend une vue entière big endian de ce tampon d'octet
IntBuffer ibBE = ((ByteBuffer)[Link]()).asIntBuffer();
l'ordre natif [Link]([Link]()); // affiche BIG_ENDIAN
Les "vues" d'un tampon d'octet ont l'ordre du [Link]([Link]()); // affiche 134480385
tampon d'octet au moment de la création de la // On prend une vue entière en little endian du même tampon
IntBuffer ibLE = [Link](ByteOrder.LITTLE_ENDIAN).asIntBuffer();
vue. [Link]([Link]()); // affiche LITTLE_ENDIAN
[Link]([Link](1)); // affiche 134480385
[Link]([Link](0)); // affiche 16909320...
// ... ordre inverse
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 23 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 24
Les tampons de caractères Appendable et Readable (jdk1.5)
Les tampons de caractères CharBuffer implantent CharBuffer implante ces deux interfaces
l'interface CharSequence
Appendable: un truc auquel on peut ajouter des
Permet d'utiliser les expressions régulières char
directement sur les tampons (Pattern: matcher(), Soit un caractère tout seul: Appendable append(char c)
matches(), split()) Soit tout ou partie d'une CharSequence :
Pour toute recherche, penser à revenir au début du Appendable append(CharSequence csq) et
tampon, par exemple avec flip(), car seuls les Appendable append(CharSequence csq, int start, int
caractères restants à lire sont pris en compte end)
toString() retourne la chaîne entre la position Exemples: BufferedWriter, CharBuffer, FileWriter,
courante et la limite OutputStreamWriter, PrintStream, PrintWriter,
StringBuffer, StringBuilder, StringWriter, Writer...
wrap() peut accepter (en plus d'un char[]), n'importe
quel CharSequence : String, StringBuffer ou
Readable: un truc dans lequel on peut lire des char
CharBuffer
int read(CharBuffer cb)
Dans ces derniers cas, le tampon est en lecture
Retourne le nombre de caractères lus et placés dans le
seule. CharBuffer cb, ou -1 si le Readable n'a plus rien à lire
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 25 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 26
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 35 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 36
Classes/interfaces
Les canaux (channels) fondamentales
Représentent des connexions ouvertes vers des
[Link].*
Channel
entités capables d'effectuer des opérations
close(), isOpen()
d'entrées-sorties comme des fichiers, des sockets ou
ReadableByteChannel
des tubes
int read(ByteBuffer)
Un canal est ouvert à sa création.
Tente de lire au plus
Il ne peut plus être utilisé une fois qu'il est fermé. remaining() octets.
À la différence des flots, il peut être utilisé en mode WritableByteChannel
bloquant ou non bloquant (on y reviendra plus tard). int write(ByteBuffer)
En mode non bloquant, toutes les lectures/écritures Tente d'écrire au plus
retournent immédiatement, même si rien n'est lu ou remaining() octets
écrit. ByteChannel
On peut alors utiliser un sélecteur pour attendre en Hérite des deux
même temps la possibilité d'effectuer des entrées-sorties
sur différents canaux.
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 37 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 38
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 43 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 44
Fichiers mappés en mémoire Canaux sur les tubes
Permet de voir un fichier comme un tampon d'octets Comme les tubes des flots ([Link])
dans lequel on peut lire ou écrire PipedReader,PipedWriter,PipedInputStream, PipedOutputStream
Opérations beaucoup plus rapides mais
Faire communiquer simplement deux processus légers
Coût pour réaliser le mapping (alloué direct via malloc) Ce qui est écrit un thread est lu dans l'ordre par l'autre
MappedByteBuffer extends ByteBuffer
À la différence des tubes des flots, ceux des canaux
Obtenu par map() sur un FileChannel avec arguments: [Link] : Pipe, [Link] et [Link]
[Link] Sont des objets qui ont une « réalité système »
READ_ONLY, fichier ne peut pas être modifié via ce tampon
Créés avec la méthode statique open() de type Pipe
READ_WRITE, le fichier est modifié (avec un délai, cf. Retourne un objet de type Pipe, sur lequel on peut faire
force())
[Link] sink() pour écrire des données
PRIVATE crée une copie privée du fichier. Aucune (AbstractSelectableChannel,WritableByteChannel,GatheringByteChannel)
modification répercutée
[Link] source() pour lire des données
long position (AbstractSelectableChannel,ReadableByteChannel,ScatteringByteChannel)
long taille Les deux canaux supportent le passage en mode non
Le fichier mappé reste valide jusqu'à ce que le bloquant
MappedByteBuffer soit garbage-collecté
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 45 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 46
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 47 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 48
Envoi sur un DatagramChannel Réception depuis un DatagramChannel
int send(ByteBuffer src, SocketAddress target) SocketAddress receive(ByteBuffer dst)
Provoque l'envoi des données restantes du tampon Par défaut, méthode bloquante tant que rien n'est reçu
src vers target par la socket
Semblable à un write() du point de vue du canal Au plus [Link]() octets peuvent être reçus
Le reste est tronqué
Si canal bloquant, la méthode retourne lorsque
Retourne l'adresse de socket (IP+port) de l'émetteur des
tous les octets ont été émis (leur nombre est
données
retourné)
Si canal non bloquant, soit tout est reçu, soit le
Si canal non bloquant, la méthode émet tous les tampon n'est pas modifié et la méthode retourne null
octets ou aucun
Bug: on ne peut pas limiter le temps d'attente en lecture,
Si une autre écriture est en cours sur la socket par comme c'est le cas avec
une autre thread, l'invocation de cette méthode [Link]()
bloque jusqu'à ce que la première opération soit
Dans ce cas, c'est ignoré par le flot de lecture sur le canal
terminée Si besoin, il faut le faire « à la main »
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 49 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 50
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 51 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 52
Exemple de serveur UDP avec canaux Canaux vers les sockets TCP
[Link] et SocketChannel
Acceptation de connexion et représentation des connexions
// Création d'un objet canal UDP (non attaché)
DatagramChannel dc = [Link]();
SocketChannel représente un canal sur une socket TCP
InetSocketAddress isa = new InetSocketAddress(port); Objet canal de socket TCP (non attaché) retourné par open()
// attachement (explicite) de la socket sous-jacente Attachement éventuel de la socket sous-jacente Socket
// au port d'écoute socket() Par un appel à bind() sur cet objet Socket
[Link]().bind(isa);
ByteBuffer bb = [Link](512);
ServerSocketChannel
Objet canal d'écoute de connexions entrantes retourné par
while (true) {
SocketAddress sender = [Link](bb); // réception open()
[Link](); // traitement éventuel N'est que l'abstraction d'un canal sur une ServerSocket,
[Link](bb,sender); // réponse à l'émetteur récupérable par la méthode socket()
[Link](); Il faut attacher cette ServerSocket par bind() avant de
} pouvoir accepter des connexions (sinon,
NotYetBoundException)
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 53 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 54
ServerSocketChannel SocketChannel
Appeler accept() sur le canal pour accepter des
boolean connect(SocketAddress remote) sur le canal
Si SocketChannel en mode bloquant, l'appel à connect() bloque
connexions (la server socket sous-jacente doit être et retourne true quand la connexion est établie, ou lève
attachée) IOException
Si le canal est bloquant, l'appel bloque Si SocketChannel en mode non bloquant, l'appel à connect()
Retourne un SocketChannel quand la connexion est Peut retourner true immédiatement (connexion locale, par exemple)
acceptée ou Retourne false le plus souvent: il faudra plus tard appeler
finishConnect()
Lève une IOException si une erreur d'entrée-sortie Tant que le canal est non connecté, les opérations
arrive d'entrée/sortie lèvent NotYetConnectedException (peut être
Si le canal est non bloquant, retourne testé par isConnected())
immédiatement Un SocketChannel reste connecté jusqu'à ce qu'il soit fermé
Possibilité de faire des half-closed et des fermetures asynchrones
null s'il n'y a pas de connexion pendante AsynchronousCloseException (si canal fermé alors qu'une opération
Quel que soit le mode (bloquant / non bloquant) du d'écriture en cours était bloquée, ou pendant l'opération de
ServerSocketChannel le SocketChannel retourné est connection)
initialement en mode bloquant ClosedByInterruptException (si autre thread interromp la thread
courante lorsqu'elle effectue une opération, IO, connection, etc.)
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 55 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 56
Établissement de connexion Communication sur canal de socket TCP
Méthode finishConnect() Une fois la connexion établie, le canal de socket se
Si connexion a échoué, lève IOException
comporte comme un canal en lecture et écriture
Si pas de connexion initiée, lève
NoConnectionPendingException
extends AbstractSelectableChannel et implements
ByteChannel, ScatteringByteChannel,
Si connexion déjà établie, retourne immédiatement true
GatheringByteChannel
Si la connexion n'est pas encore établie Méthodes read() et write()
Si mode non bloquant, retourne false En mode bloquant, write() assure que tous les octets
Si mode bloquant, l'appel bloque jusqu'au succès ou
à l'échec de la connexion (retourne true ou lève une
seront écrits mais read() n'assure pas que le tampon sera
exception) rempli (au moins un octet lu ou détection de fin de
Si cette méthode est invoquée lorsque des opérations de connexion: retourne -1)
lecture ou d'écriture sur ce canal sont appelés En mode non bloquant, lecture comme écriture peuvent
Ces derniers sont bloqués jusqu'à ce que cette ne rien faire et retourner 0.
méthode retourne Fermeture de socket par close() entraine la fermeture
Si cette méthode lève une exception (la connexion du canal
échoue)
Alors le canal est fermé
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 57 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 58
Objets utilisés avec les canaux configurés en mode Enregistrement d'un canal auprès d'un sélecteur
non bloquant qui héritent de SelectableChannel Se fait par un appel, sur le canal à enregistrer, de:
Ces canaux peuvent être enregistrés auprès d'un SelectionKey register(Selector sel, int ops) ou
sélecteur après avoir été configurés non bloquant SelectionKey register(Selector sel, int ops, Object att)
(configureBlocking(false)) ops représente les opérations "intéressantes" pour ce
[Link] : les instances sont créées par sélecteur
appel à la méthode statique open() et fermés par close() SelectionKey retourné représente la clé de sélection de
ce canal, qui permet de connaître, outre le sélecteur:
channel() renvoie le canal lui même
interestOps() renvoie les opérations « intéressantes »
attachment() renvoie l'objet att éventuellement attaché
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 59 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 60
Autour des sélecteurs Utilisation des sélecteurs
Etienne Duris © Université de Marne la Vallée - Février 2007 Page 61 Etienne Duris © Université de Marne la Vallée - Février 2007 Page 62
1. Chaque clé de canceled-key set est supprimée Les sélecteurs sont "thread-safe", mais pas leurs
De chacun des ensembles dans lesquels elle est présente ensembles
À la fin de 1. canceled-key set est vide Une opération de sélection synchronise:
2. L'OS est interrogé 1. sur le sélecteur
Pour savoir quels canaux sont prêts à réaliser une 2. sur le key-set
opération qui est décrite par sa clé comme "d'intérêt" au 3. sur le selected-key set
début de l'opération de sélection
Et sur le canceled-key set pendant les étapes 1. et 3. de la
Pour chacun des canaux prêts à faire quelque chose sélection
Soit sa clé n'était pas déjà dans selected-key set, alors
Exemples