[JAVA Basics] - Explication détaillée du mode synchrone non bloquant NIO

[JAVA Basics] - Explication détaillée du mode synchrone non bloquant NIO

I. Aperçu

NIO (Non-Blocking IO) est un moyen synchrone et non bloquant de traiter les données IO. Le mode d'implémentation du serveur est une requête et un thread, c'est-à-dire que la demande de lien envoyée par le client sera enregistrée dans le sélecteur et le sélecteur démarrera un thread pour le traitement uniquement lorsqu'il y aura une demande d'E/S sur la connexion.

2. Concepts communs

  • Synchrone : La méthode appelante fait référence à Application. Lorsque l'appelant lance un appel de fonction, l'appel ne reviendra pas tant que le résultat de la fonction n'est pas obtenu. C'est-à-dire que l'appelant attendra que l'appelé renvoie le résultat de la fonction.
  • Asynchrone : Lorsque l'appelant lance un appel de fonction, le résultat de la fonction qui n'est pas obtenu est renvoyé immédiatement. Par la suite, l'appelé informe l'appelant de la structure de la fonction via des rappels et d'autres moyens. Autrement dit, l'appelant obtient le retour immédiatement, mais le retour n'inclut pas les résultats de l'exécution.

La synchronisation et la communication asynchrone mettent l'accent sur le mécanisme de communication des messages (communication synchrone/communication asynchrone). La soi-disant synchronisation signifie que lorsqu'un « appel » est émis, « l'appel » ne reviendra pas tant que le résultat n'est pas obtenu. Mais une fois l’appel renvoyé, vous obtenez la valeur de retour. En d’autres termes, « l’appelant » attend activement le résultat de cet « appel ». Asynchrone est le contraire : une fois « l'appel » émis, l'appel revient directement, donc aucun résultat n'est renvoyé. En d’autres termes, lorsqu’un appel de procédure asynchrone est émis, l’appelant n’obtient pas le résultat immédiatement. Au lieu de cela, une fois « l'appel » émis, « l'appelé » informe l'appelant via son statut et sa notification, ou gère l'appel via une fonction de rappel.

  • Blocage : Lorsqu'un thread lance un appel, le thread sera bloqué avant le retour de l'appel. Dans cet état, les droits d'utilisation actuels du processeur seront transférés et suspendus ; c'est-à-dire que l'appelant attendra le résultat de l'appel et que l'appel bloque le thread de l'appelant. Le thread n'exécute pas de traitement.
  • Non bloquant : lorsqu'un thread lance un appel, l'appel reviendra immédiatement pour éviter que le thread ne soit bloqué. Cependant, le résultat renvoyé n'est que la valeur de l'état actuel de l'appelé. Lorsqu'il est réellement utilisé, l'appelant doit interroger jusqu'à ce que le résultat renvoyé réponde aux attentes (jusqu'à ce que les données soient prêtes).

Le blocage et le non-blocage mettent l'accent sur l'état du programme en attendant le résultat de l'appel (message, valeur de retour). L'appel bloquant signifie que le thread en cours sera suspendu avant que le résultat de l'appel ne soit renvoyé. Le thread appelant ne reviendra qu'après avoir obtenu le résultat. Un appel non bloquant signifie que l'appel ne bloquera pas le thread en cours jusqu'à ce que le résultat ne puisse pas être obtenu immédiatement. Pour les appels synchrones, plusieurs fois le thread actuel est toujours actif, mais logiquement la fonction actuelle ne revient pas, c'est-à-dire que rien n'est fait en attendant de manière synchrone, occupant des ressources en vain.

  • Blocage synchrone IO [BIO - BlockingIO] : Dans cette méthode, après avoir lancé une opération IO, le processus utilisateur doit attendre la fin de l'opération IO. Ce n'est que lorsque l'opération IO est réellement terminée que le processus utilisateur peut s'exécuter. Le modèle IO traditionnel de JAVA appartient à cette méthode.
  • IO synchrone non bloquante [IO non bloquantes] : dans cette méthode, le processus utilisateur lance une opération IO et peut revenir faire autre chose plus tard, mais le processus utilisateur doit demander de temps en temps si l'opération IO est prête, ce qui oblige le processus utilisateur à continuer à demander, introduisant ainsi un gaspillage inutile de ressources CPU. Parmi eux, le NIO de JAVA appartient actuellement aux IO synchrones non bloquantes.
  • Blocage asynchrone des IO [IO Multiplexing] : Cette méthode signifie qu'après que l'application a lancé une opération IO, elle n'attend pas la fin de l'opération IO du noyau. Une fois que le noyau a terminé l'opération IO, il en informera l'application. C'est en fait La différence la plus critique entre synchrone et asynchrone. , la synchronisation doit attendre ou demander activement si l'IO est terminée, alors pourquoi est-elle dite bloquée ? Parce que cela se fait via l'appel système select et que l'implémentation de la fonction select elle-même est bloquante. L'avantage d'utiliser la fonction select est qu'elle peut surveiller plusieurs descripteurs de fichiers en même temps, améliorant ainsi la concurrence du système !
  • IO asynchrone non bloquant [Asynchronous IO] : Dans ce mode, le processus utilisateur n'a besoin que de lancer une opération d'E/S, puis de revenir immédiatement. Une fois l'opération d'E/S terminée, l'application sera informée que l'opération d'E/S est terminée. cette fois, le processus utilisateur n'a besoin que de tant que les données doivent être traitées, les opérations de lecture et d'écriture d'E/S réelles ne sont pas nécessaires, car les opérations de lecture ou d'écriture d'E/S réelles ont été effectuées par le noyau. Actuellement, ce modèle IO n'est pas pris en charge en Java.

3. Principe de mise en œuvre de NIO

Insérer la description de l'image ici

Le NIO de Java est principalement composé de trois parties principales : Channel , Buffer et Selector .

Toutes les E/S démarrent à partir d'un canal dans NIO. Les données peuvent être lues du canal vers le tampon, ou écrites du tampon vers le canal. Il existe plusieurs types de canaux, parmi lesquels les plus couramment utilisés sont FileChannel , DatagramChannel , SocketChannel , ServerSocketChannel , etc. Ces canaux couvrent les E/S des réseaux UDP et TCP et les E/S des fichiers.

Un tampon est essentiellement un bloc de mémoire dans lequel des données peuvent être écrites et à partir desquelles des données peuvent ensuite être lues. Cette mémoire est conditionnée sous forme d'objet NIO Buffer et fournit un ensemble de méthodes pour accéder facilement à cette mémoire. Les principales implémentations de Buffer dans Java NIO incluent CharBuffer , ByteBuffer , ShortBuffer , IntBuffer , LongBuffer , FloatBuffer et DoubleBuffer . Ces tampons couvrent les types de données de base que vous pouvez envoyer via IO, à savoir byte , short , int , long , float , double , char .

L'objet Buffer contient trois attributs importants, à savoir capacité , position et limite . La signification de la position et de la limite dépend du fait que le Buffer soit en mode lecture ou en mode écriture. Mais quel que soit le mode dans lequel se trouve le Buffer, la signification de la capacité est toujours la même.

Capacité : En tant que bloc mémoire, Buffer a une valeur maximale fixe, qui est la capacité. Le tampon ne peut écrire que la capacité des données. Une fois le tampon plein, il doit être vidé avant de pouvoir continuer à écrire des données.

position : lors de l'écriture de données dans le tampon, position représente la position actuelle. La valeur de la position initiale est 0. Lorsqu'une donnée est écrite dans le tampon, la position passe à l'unité tampon suivante où les données peuvent être insérées. La position maximale peut être la capacité-1. Lorsque des données sont lues, elles sont également lues à partir d’un emplacement spécifique. Lors du passage du tampon du mode écriture au mode lecture, la position sera réinitialisée à 0. Lorsque les données sont lues à partir de la position du tampon, la position avance jusqu'à la position lisible suivante.

limit : En mode écriture, la limite du Buffer indique la quantité maximale de données pouvant être écrites dans le Buffer. A ce moment, la limite est égale à la capacité. Lorsque vous passez le tampon en mode lecture, la limite indique la quantité maximale de données que vous pouvez lire. À ce moment, la limite sera définie sur la valeur de position en mode écriture.

Selector permet à un seul thread de traiter plusieurs canaux. Si votre application ouvre plusieurs connexions (canaux), mais que le trafic de chaque connexion est très faible, l'utilisation de Selector sera très pratique. Pour utiliser un sélecteur, vous devez enregistrer un canal auprès du sélecteur, puis appeler sa méthode select(). Cette méthode sera bloquée jusqu'à ce qu'une chaîne enregistrée ait un événement prêt. Une fois cette méthode renvoyée, le thread peut gérer ces événements, tels que l'arrivée de nouvelles connexions, la réception de données, etc.

4. Implémentation du code NIO

Implémentation client
  • étape

    1. Créer un canal SocketChannel
    2. Changer le mode asynchrone non bloquant configureBlocking(false)
    3. Définir la taille du tampon ByteBuffer.allocate(1024)
    4. Les valeurs sont écrites dans le tampon buffer.put(input.getBytes())
    5. La valeur dans le tampon est écrite dans le canal canal.write()
  • démo de code

    public static void main(String[] args) throws IOException {
          
          
          //创建通道
          SocketChannel channel=SocketChannel.open(new InetSocketAddress("127.0.0.1",6001));
          //切换异步非阻塞模式
          channel.configureBlocking(false);
          //设置缓冲去大小
          ByteBuffer buffer=ByteBuffer.allocate(1024);
          System.out.println("输入传输值:");
          //获取键盘输入的值
          Scanner scanner = new Scanner(System.in);
          while (scanner.hasNext()){
          
          
              String input=scanner.next();
              //把获取的值写入缓冲区中
              buffer.put(input.getBytes());
              buffer.flip();
              //把缓冲区中的值写入通道中
              channel.write(buffer);
              buffer.clear();
           }
           channel.close();
     }
    
Implémentation côté serveur
  • étape

    1. Créer un canal ServerSocketChannel
    2. Changer le mode asynchrone non bloquant configureBlocking(false)
    3. Lier la connexion
    4. Obtenez le selectorSelector open = Selector.open()
    5. Enregistrez le canal dans le sélecteur et spécifiez d'écouter les événements d'acceptation
    6. Acquisition à la ronde des événements sélectionnés qui sont prêts
    7. Obtenez tous les événements d'écoute enregistrés du sélecteur actuel
    8. Préparez les événements
    9. Déterminez quels événements sont prêts
    10. Acceptez, prêt, obtenez la connexion client
    11. Définir le mode asynchrone non bloquant
    12. Enregistrez la chaîne sur le serveur
  • démo de code

    public static void main(String[] args) throws IOException {
          
          
        //创建通道
        ServerSocketChannel channel=ServerSocketChannel.open();
        //切换到异步非阻塞模式
        channel.configureBlocking(false);
        //绑定链接
        channel.bind(new InetSocketAddress(6001));
            //获取选择器
            Selector open = Selector.open();
            //将通道注册到选择器,并指定监听接受事件
            channel.register(open, SelectionKey.OP_ACCEPT);
            //轮训式获取选择已经准备就绪的事件
            while(open.select() > 0) {
          
          
                //获取当前选择器所有注册的监听事件
                Iterator<SelectionKey> it = open.selectedKeys().iterator();
                while(it.hasNext()) {
          
          
                    //获取准备就绪的事件
                    SelectionKey sk = it.next();
                    //判断是什么事件准备就绪
                    if(sk.isAcceptable()) {
          
          
                        //接受就绪,获取客户端连接
                        SocketChannel sc = channel.accept();
                        //设置非阻塞异步模式
                        sc.configureBlocking(false);
                        //将通道注册到服务器上
                        sc.register(open, SelectionKey.OP_READ);
                    } else if(sk.isReadable()) {
          
          
                        //获取当前选择器就绪的通道
                        SocketChannel s =  (SocketChannel) sk.channel();
                        ByteBuffer bb = ByteBuffer.allocate(1024);
                        int len = 0;
                        while((len = s.read(bb)) > 0) {
          
          
                            bb.flip();
                            System.out.println(new String(bb.array(),0,len));
                            bb.clear();
                        }
                   }
              }
             it.remove();
         }
    
    }
    

5. Résumé du NIO synchrone non bloquant

Caractéristiques synchrones non bloquantes : Le thread d'application doit continuellement effectuer des appels système IO et demander si les données sont prêtes. Dans le cas contraire, continuez l'interrogation jusqu'à ce que l'appel système IO soit terminé.

Caractéristiques des IO synchrones non bloquantes : Chaque appel système IO initié peut revenir immédiatement pendant que le noyau attend des données. Le thread utilisateur ne sera pas bloqué et les performances en temps réel sont meilleures.

Inconvénients des E/S synchrones non bloquantes : interrogation constante du noyau, ce qui prendra beaucoup de temps CPU et est inefficace.

De manière générale, les E/S synchrones non bloquantes ne sont pas disponibles dans les scénarios d'application à haute concurrence. Les serveurs Web généraux ne conviennent pas à ce modèle IO. Ce modèle IO est généralement rarement utilisé directement, mais la fonctionnalité IO non bloquante est utilisée dans d'autres modèles IO.

Je suppose que tu aimes

Origine blog.csdn.net/songjianlong/article/details/132223450
conseillé
Classement