Le système suivant fournit une introduction approfondie au mécanisme et à l'utilisation de la fonction de Java Nio par le biais de principes, de processus, etc., apprenons-le.
Préface
Cet article explique principalement le mécanisme IO en Java
Divisé en deux pièces:
La première partie explique le mécanisme IO sous Multithreading. La deuxième partie explique comment optimiser le gaspillage des ressources CPU sous le mécanisme IO (New IO)
Serveur d'écho
Je n'ai pas besoin d'introduire le mécanisme de socket sous un seul thread. Si vous ne le savez pas, vous pouvez vérifier les informations sous tant de threads. Et si vous utilisez des sockets?
Nous utilisons le serveur d'écho le plus simple pour aider tout le monde à comprendre
Tout d'abord, jetons un coup d'œil au diagramme de workflow du serveur et du client sous le multi-threading:
Vous pouvez voir que plusieurs clients envoient des demandes au serveur en même temps
Le serveur a fait une mesure pour permettre à plusieurs threads de faire correspondre le client correspondant.
Et chaque thread remplit ses demandes de clients seuls
Une fois le principe terminé, voyons comment il est mis en œuvre
Ici, j'ai écrit un serveur simple
Utilisez la technologie de pool de threads pour créer des threads (j'ai commenté la fonction de code spécifique):
classe publique MyServer {private static ExecutorService ExecutorService = exécutor.NewCachedThreadPool (); // Créer un handlemsg de classe statique privée de thread Pool implémente Runnable {// Une fois qu'il y a une nouvelle demande client, créez ce thread pour traiter le client de socket; // Créer un client public HandleMsg (Socket Client) {// Construire un paramètre liant ce.client = client; } @Override public void run () {BufferedReader BufferedReader = null; // Créer un flux de cache de caractères PrintWriter PrintWriter = NULL; // Créer un flux d'écriture de caractères essayez {bufferedReader = new BufferedReader (new inputStreamReader (client.getInputStream ())); // obtient le flux d'entrée du client printwriter = new printwriter (client.getOutputStream (), true); // obtient le flux de sortie du client, vrai consiste à actualiser la chaîne inputLine = null; long a = System.currenttimemillis (); while ((inputLine = bufferedReader.readline ())! = null) {printwriter.println (inputline); } long b = System.currenttimemillis (); System.out.println ("Ce thread a pris:" + (ba) + "secondes!"); } catch (ioException e) {e.printStackTrace (); } enfin {try {bufferedReader.close (); printwriter.close (); client.close (); } catch (ioException e) {e.printStackTrace (); }}}} public static void main (String [] args) lève ioException {// Le thread principal du serveur est utilisé pour écouter les demandes du client dans une boucle Serversocket Server = new Serversocket (8686); // Créer un serveur avec le port 8686 Socket Client = NULL; while (true) {// Loop écouter client = server.accept (); // Le serveur écoute un System de demande client.out.println (client.getRemotesocketAddress () + "La connexion client est réussie!"); ExecutorService.Submit (new HandleMSG (client)); // Mettez la demande du client dans le thread handlmsg via le pool de threads pour le traitement}}}Dans le code ci-dessus, nous utilisons une classe pour écrire un serveur d'écho simple pour activer l'écoute du port dans le thread principal à l'aide d'une boucle morte.
Client simple
Avec un serveur, nous pouvons y accéder et envoyer des données de chaîne. La fonction du serveur est de retourner ces chaînes et d'imprimer le thread en prenant du temps.
Écrivons un client simple pour répondre au serveur:
classe publique MyClient {public static void main (String [] args) lève ioException {socket client = null; Printwriter printwriter = null; BufferedReader BufferedReader = null; essayez {client = new socket (); client.connect (new InetsocketAddress ("localhost", 8686)); printwriter = new printwriter (client.getOutputStream (), true); printwriter.println ("bonjour"); printwriter.flush (); BufferedReader = new BufferedReader (new InputStreamReader (client.getInputStream ())); // Lire les informations renvoyées par le serveur et le STUPPORT SYSTEM.out.println ("Les informations du serveur sont:" + BufferedReader.Readline ()); } catch (ioException e) {e.printStackTrace (); } enfin {printwriter.close (); BufferedReader.Close (); client.close (); }}}Dans le code, nous utilisons le flux de caractères pour envoyer une chaîne Hello. Si le code est bien, le serveur renvoie des données Hello et imprimera les informations de journal que nous définissons.
Affichage des résultats du serveur d'écho
Courons:
1. Ouvrez le serveur et activez l'écoute en boucle:
2. Ouvrez un client:
Vous pouvez voir que le client imprime le résultat de retour
3. Vérifiez le journal du serveur:
Très bon, une simple programmation de socket multithread est implémentée
Mais pensez-y:
Si un client demande, ajoutez le sommeil pendant l'écriture IO au serveur,
Faites en sorte que chaque demande occupe le thread du serveur pendant 10 secondes
Ensuite, il y a beaucoup de demandes de clients, chacun prenant aussi longtemps
Ensuite, les capacités d'unification du serveur seront considérablement réduites
Ce n'est pas parce que le serveur a de nombreuses tâches lourdes, mais juste parce que le thread de service attend IO (parce que l'accepter, lire et écrire bloque tous)
Il est très impayé pour laisser les processeurs à grande vitesse attendre leur réseau inefficace IO
Que dois-je faire pour le moment?
Nio
Le nouvel IO a résolu avec succès le problème ci-dessus. Comment cela l'a-t-il résolu?
L'unité minimale pour IO pour traiter les demandes des clients est le thread
Nio utilise une unité qui est un niveau plus petit qu'un fil: canal (canal)
On peut dire qu'un seul fil est nécessaire dans NIO pour terminer toutes les réceptions, la lecture, l'écriture et d'autres opérations.
Pour apprendre Nio, vous devez d'abord comprendre ses trois points de base
Sélecteur, sélecteur
Tampon, tampon
Canal, canal
Le blogueur n'est pas talentueux, alors il a dessiné une image laide pour approfondir son impression ^. ^
Donnez-moi un autre diagramme de flux de travail NIO sous TCP (très difficile à tracer les lignes ...)
Comprenez-le à peu près, allons étape par étape
Tampon
Tout d'abord, vous devez savoir ce qu'est un tampon
L'interaction des données n'utilise plus de flux comme les mécanismes IO
Au lieu de cela, utilisez un tampon (tampon)
Le blogueur pense que les images sont les plus faciles à comprendre
donc...
Vous pouvez voir où se trouve le tampon dans l'ensemble du flux de travail
Jetons un coup d'œil aux vrais. Le code spécifique dans la figure ci-dessus est le suivant:
1. Tout d'abord d'espace au tampon, en octets
ByteBuffer byteBuffer = byteBuffer.Allocation (1024);
Créez un objet ByteBuffer et spécifiez la taille de la mémoire
2. Écrivez des données dans le tampon:
1). Données du canal à tampon: canal.read (bytebuffer); 2). Données du client au tampon: bytebuffer.put (...);
3. Lire les données du tampon:
1). Données du tampon à la chaîne: Channel.Write (byteBuffer); 2). Données du tampon au serveur: bytebuffer.get (...);
Sélecteur
Le sélecteur est le cœur de Nio, c'est l'administrateur du canal
En exécutant la méthode de blocage select (), écoutez si le canal est prêt
Une fois les données lisibles, la valeur de retour de cette méthode est le nombre de SELECTIONKES
Ainsi, le serveur exécute généralement la boucle morte de la méthode select () jusqu'à ce que le canal soit prêt et commence ensuite à fonctionner
Chaque canal liera un événement au sélecteur, puis générera un objet SelectionKey.
Ce qui doit être noté est:
Lorsque le canal et le sélecteur sont liés, le canal doit être en mode non bloquant.
FileChannel ne peut pas passer en mode non bloquant car il n'est pas un canal de douille, donc FileChannel ne peut pas lier les événements avec le sélecteur
Il existe quatre types d'événements dans NIO:
1.Selectionkey.op_connect: événement de connexion
2.Selectionkey.op_accept: recevoir des événements
3.Selectionkey.op_read: Lire les événements
4.Selectionkey.op_write: Écrivez des événements
Canal
Il y a quatre canaux au total:
FileChannel: agit sur le flux de fichiers IO
DatagramChannel: il fonctionne sur le protocole UDP
Socketchannel: il fonctionne sur le protocole TCP
SERVERSOCHETHETCHALnel: il agit sur le protocole TCP
Cet article explique NIO via le protocole TCP couramment utilisé
Prenons l'exemple de serversocketchannel:
Ouvrez un canal Serversocketchannel
Serversocketchannel serversocketchannel = serversocketchannel.open ();
Fermez le canal Serversocketchannel:
SERVERSOCHETHETCHANNEL.CLOSE ();
Boucle de boucle de socketchannel:
while (true) {socketchannel socketchannel = serversOCHETCHAnnel.Accept (); clientChannel.ConfigureBlocking (false);} clientChannel.configureBlocking(false); La déclaration consiste à définir ce canal sur le non-blocage, c'est-à-dire que le contrôle libre asynchrone du blocage ou du non-blocage est l'une des caractéristiques du NIO
Sélection de sélection
SelectionKey est le composant central de l'interaction entre les canaux et les sélecteurs
Par exemple, liez un sélecteur sur Socketchannel et enregistrez-le en tant qu'événement de connexion:
Socketchannel ClientChannel = socketchannel.open (); clientChannel.ConfigureBlocking (false); clientChannel.Connect (new InetsocketAddress (port)); clientChannel.Register (Selector, SelectionKey.OP_Connect);
Le noyau est dans la méthode Register (), qui renvoie un objet SELECTIONKEK
Pour détecter les événements de canal, c'est quel type d'événement vous pouvez utiliser la méthode suivante:
SELECTIONKEY.IsACceptable (); SELECTIONKEKE.IsConnectable (); SELECTIONKEY.IsReadable (); SELECTIONKEY.IsWitable ();
Le serveur effectue des opérations correspondantes dans le sondage via ces méthodes
Bien sûr, les clés liées au canal et au sélecteur peuvent également être obtenues à leur tour.
Channel Channel = SelectionKey.Channel (); Selector Selector = SelectionKey.Selector ();
Lors de l'enregistrement des événements sur la chaîne, nous pouvons également lier un tampon:
ClientChannel.Register (Key.Selector (), SELECTIONKEY.OP_READ, BYTEBUFFER.ALLOCEDIRECT (1024));
Ou lier un objet:
SELECTIONKEY.ATTACH (Object); Object AnthorObj = SelectionKey.Attachment ();
Le serveur TCP de Nio
Après avoir tellement parlé, nous examinerons le code le plus simple et le plus de base (il n'est pas élégant d'ajouter autant de commentaires, mais il est pratique pour tout le monde de comprendre):
Package cn.blog.test.niotest; import java.io.ioException; import java.net.inetsocketAddress; import java.nio.bytebuffer; import java.nio.channels. *; import java.nio.charset.charset; import java.util.iterator; import java.util.set; class public mynioser {private Selector; // Créer un sélecteur PRIVATE FINAL static int port = 8686; private final static int buf_size = 10240; private void initserver () lève ioException {// Create Channel Manager Object Selector this.selector = selector.open (); // Créer un canal d'objet de canal sertSoCHETCHELNAL CHANNEL = SERVERSOCHETCHANNEL.Open (); channel.configureBlocking (false); // Définissez le canal sur canal non bloquant.socket (). Bind (new IneTSocketAddress (port)); // lier le canal sur le port 8686 // lier le gestionnaire de canaux et le canal ci-dessus, et enregistrez l'événement OP_ACcept pour le canal // Après avoir enregistré l'événement, Selector.Select () reviendra (une clé). Si l'événement n'atteint pas Selector.Select (), il bloquera SelectionKey SelectionKey = Channel.Register (Selector, SelectionKey.op_Accept); while (true) {// Poll Selector.Select (); // Il s'agit d'une méthode de blocage, attendez que les données soient lisibles et la valeur de retour est le nombre de clés (il peut y avoir plusieurs) clés de set = Selector.SelectedKeys (); // Si le canal a des données, accédez aux touches générées dans les clés de la collection Iterator iterator = keys.iterator (); // Obtenez l'itérateur de cette collection de touches while (iterator.hasnext ()) {// Utilisez l'itérateur pour traverser la collection selectionKey key = (selectionKey) iterator.next (); // Obtenez une instance clé dans la collection iterator.remove (); // N'oubliez pas de supprimer cet élément dans l'itérateur après avoir obtenu l'instance de clé actuelle, ce qui est très important, sinon une erreur se produira si (key.isacceptable ()) {// juger si le canal représenté par la clé actuelle est à l'état acceptable, et si oui, Doaccept (clé); } else if (key.isreadable ()) {doread (key); } else if (key.iswitable () && key.isvalid ()) {dowrite (key); } else if (key.isconnectable ()) {System.out.println ("Connectable!"); }}}} public void Doaccept (SELECTIONKEKEY KEY) lève IOException {ServersOCHAUX ENCHANNELSCHANNEL = (Serversocketchannel) key.channel (); System.out.println ("Serversocketchannel écoute dans une boucle"); Socketchannel clientChannel = serverChannel.Accept (); clientChannel.ConfigureBlocking (false); ClientChannel.Register (Key.Selector (), SelectionKey.op_read); } public void Doread (SELECTELKEY KEY) lève ioException {socketchannel clientChannel = (socketchannel) key.channel (); ByteBuffer byteBuffer = byteBuffer.AllOcy (buf_size); long bytesRead = clientChannel.read (byteBuffer); while (bytesRead> 0) {byteBuffer.flip (); byte [] data = bytebuffer.array (); String info = new String (data) .trim (); System.out.println ("Le message envoyé du client est:" + info); ByteBuffer.Clear (); bytesRead = clientChannel.read (byteBuffer); } if (bytesRead == - 1) {clientChannel.close (); }} public void Dowrite (SELECTIONKEKEY KEY) lève ioException {ByteBuffer ByteBuffer = ByteBuffer.Allocate (buf_size); bytebuffer.flip (); Socketchannel clientChannel = (socketchannel) key.channel (); while (bytebuffer.hasreMinging ()) {clientChannel.write (byteBuffer); } bytebuffer.compact (); } public static void main (String [] args) lève ioException {myniOServer myniOServer = new MyniOServer (); MyniOServer.InitServer (); }} J'ai imprimé le canal de moniteur et vous a dit quand le serverocketchannel a commencé à fonctionner
Si vous coopérez avec le débogage du client NIO, vous pouvez le trouver clairement. Avant d'entrer dans le sondage sélectionné ()
Bien qu'il existe déjà une clé pour l'événement d'acceptation, SELECT () ne sera pas appelé par défaut
Au lieu de cela, vous devez attendre que d'autres événements intéressants soient capturés par Select () avant d'appeler SelectionKey d'Aced
Ce n'est qu'alors que le serveurs de Bourse de base a commencé à effectuer une surveillance circulaire
En d'autres termes, dans un sélecteur, le SERVERSOCHANCHNEL est toujours maintenu.
Et serverChannel.accept(); est vraiment asynchrone (canal.configureblocking (false); dans la méthode initserver)
Si la connexion n'est pas acceptée, un null sera retourné
Si un socketchannel est connecté avec succès, ce socketchannel enregistre un événement d'écriture (lecture)
Et régler asynchrone
Client TCP de Nio
Il doit y avoir un client s'il y a un serveur
En fait, si vous pouvez comprendre pleinement le serveur
Le code du client est similaire
Package cn.blog.test.niotest; Importer java.io.ioException; import java.net.inetsocketAddress; import java.nio.bytebuffer; import java.nio.channels.selectionkey; import java.nio.channels.selector; import java.nio.channels.socketchannel; import java.itreather; MyNioclient {Selecteur privé Selecteur; // Créer un sélecteur PRIVATE FINAL static int port = 8686; private final static int buf_size = 10240; ByteBuffer statique privé ByteBuffer = byteBuffer.Allocate (buf_size); private void initClient () lève ioException {this.selector = selector.open (); Socketchannel clientChannel = socketchannel.open (); clientChannel.ConfigureBlocking (false); ClientChannel.Connect (new IneTSocketAddress (port)); ClientChannel.Register (Selector, SelectionKey.op_Connect); while (true) {Selector.Select (); Iterator <lelectionkey> iterator = selector.selectedKeys (). Iterator (); while (iterator.hasnext ()) {SELECTION KEY = iterator.next (); iterator.remove (); if (key.isconnectable ()) {doconnect (key); } else if (key.isreadable ()) {doread (key); }}} public void doconnect (SELECTIONKEKEY KEY) lève ioException {socketchannel clientChannel = (socketchannel) key.channel (); if (clientChannel.isconnectionPending ()) {clientChannel.FinishConnect (); } clientChannel.ConfigureBlocking (false); Chaîne info = "Hello Server !!!"; ByteBuffer.Clear (); bytebuffer.put (info.getBytes ("utf-8")); bytebuffer.flip (); ClientChannel.Write (ByteBuffer); //ClientChannel.Register(key.selector(),,selectionkey.op_read); clientChannel.close (); } public void Doread (SELECTELKEY KEY) lève ioException {socketchannel clientChannel = (socketchannel) key.channel (); ClientChannel.Read (ByteBuffer); byte [] data = bytebuffer.array (); String msg = new String (data) .trim (); System.out.println ("Server envoie un message:" + msg); clientChannel.close (); key.selector (). close (); } public static void main (string [] args) lève ioException {myNioClient myNioClient = new MyNioClient (); myNioclient.InitClient (); }}Résultat de sortie
Ici, j'ouvre un serveur et deux clients:
Ensuite, vous pouvez essayer d'ouvrir mille clients en même temps. Tant que votre CPU est suffisamment puissant, le serveur ne pourra pas réduire les performances en raison du blocage.
Ce qui précède est une explication détaillée de Java Nio. Si vous avez des questions, vous pouvez en discuter dans la zone de message ci-dessous.