Maison  >  Article  >  Java  >  Analyser l'utilisation et les caractéristiques des exemples Java NIO

Analyser l'utilisation et les caractéristiques des exemples Java NIO

王林
王林avant
2023-05-06 22:25:06867parcourir

Analyser lutilisation et les caractéristiques des exemples Java NIO


1. Carte mentale Java

Analyser lutilisation et les caractéristiques des exemples Java NIO

2. Modèle d'E/S

L'essence du modèle d'E/S est le type de canal utilisé pour envoyer et recevoir des données, ce qui détermine un améliore dans une large mesure les performances de communication du programme.
Java prend en charge un total de trois modèles de programmation réseau : BIO, NIO, AIO

  • BIO : synchronisation et blocage, le mode d'implémentation du service est une connexion et un thread, c'est-à-dire que lorsque le client a une demande de connexion, le serveur doit démarrer un thread pour le traitement.

  • NIO : synchrone et non bloquant, le mode d'implémentation du serveur permet à un thread de gérer plusieurs connexions de requêtes, c'est-à-dire que toutes les requêtes envoyées par le client seront enregistrées sur le multiplexeur, et le multiplexeur interroge jusqu'à ce que la connexion soit établie. La demande d’E/S sera traitée.

  • AIO : Asynchrone non bloquant, AIO introduit le concept de canal asynchrone, adopte le mode Proactor, simplifie l'écriture du programme, et démarre le thread seulement après une requête valide. Sa particularité est que le système d'exploitation le termine d'abord puis le notifie. le serveur.

3. Scénarios d'application BIO, NIO, AIO

  • La méthode BIO convient aux architectures avec un nombre de connexions relativement petit et fixe. Cette méthode a des exigences relativement élevées en ressources serveur et la concurrence est limitée aux applications. Avant JDK1.4 La seule option, mais la procédure est simple et facile à comprendre.

  • La méthode NIO convient aux architectures comportant un grand nombre de connexions et des connexions relativement courtes (opérations légères), telles que les serveurs de chat, les systèmes de barrage, les communications inter-serveurs, etc. La programmation est plus compliquée et JDK1.4 commence à la prendre en charge.

  • La méthode AIO est utilisée dans les architectures avec un grand nombre de connexions et des connexions relativement longues (opérations lourdes), comme les serveurs d'albums photos. Elle appelle entièrement l'OS pour participer aux opérations concurrentes. La programmation est plus compliquée. pour prendre en charge

4. Processus simple de programmation BIO

  • Le serveur démarre un ServerSocket ;

  • Le client démarre le Socket pour communiquer avec le serveur. Par défaut, le serveur doit établir un thread pour chacun. client pour communiquer avec lui ;

  • Après que le client ait envoyé une demande, consultez d'abord le serveur pour voir s'il y a une réponse du fil. Sinon, il attendra ou sera rejeté

  • S'il y a une réponse, le thread client attendra la fin de la requête avant de continuer à s'exécuter ;

5. Noyau NIO

NIO Il y a trois parties principales : le sélecteur, le canal et le tampon.
NIO est une programmation orientée tampon ou orientée bloc. Les données sont lues dans un tampon qu'elles traiteront plus tard. Elles peuvent être déplacées d'avant en arrière dans le tampon en cas de besoin. , vous pouvez Fournit une mise en réseau non bloquante et hautement évolutive.
HTTP2.0 utilise la technologie de multiplexage pour permettre à la même connexion de traiter plusieurs requêtes simultanément, et le nombre de requêtes simultanées est plusieurs ordres de grandeur supérieur à celui de HTTP1.1.
En bref, NIO peut gérer plusieurs requêtes avec un seul thread.

6. Comparaison entre BIO et NIO

  • BIO traite les données dans un flux, tandis que NIO traite les données dans un bloc. L'efficacité des E/S de bloc est bien supérieure à celle des E/S de flux

  •  ; BIO bloque Oui, NIO n'est pas bloquant ;

  • BIO fonctionne sur la base d'un flux d'octets et d'un flux de caractères, tandis que NIO fonctionne sur la base du canal et du tampon, et les données sont toujours lues du canal dans le tampon, ou écrites à partir de celui-ci. le tampon au canal. Le sélecteur est utilisé pour surveiller plusieurs événements de canal (tels que les demandes de connexion, l'arrivée de données, etc.), de sorte qu'un seul thread peut surveiller plusieurs canaux clients.

7. Schéma des trois principes fondamentaux de NIO

Analyser lutilisation et les caractéristiques des exemples Java NIO
Description de l'organigramme :

  • Le sélecteur correspond à un thread, et un thread correspond à plusieurs canaux (connexions) 

  •  ; il y a trois canaux Inscrivez-vous au sélecteur //Programme ;

  • Chaque canal correspondra à un tampon

  • Le canal vers lequel le programme bascule est déterminé par les événements, et l'événement est un concept important ; Le sélecteur répondra en fonction des différents événements activés sur chaque canal ;

  • Le tampon est un bloc de mémoire et il y a un tableau sur la couche inférieure

  • les données sont lues et écrites via le tampon, ce qui est le même que celui-ci ; BIO. BIO est soit un flux d'entrée, soit un flux de sortie, qui ne peut pas être bidirectionnel, mais le tampon de NIO peut être lu ou écrit et doit être commuté par la méthode flip ; l'état du système d'exploitation sous-jacent, tel que Linux. Le canal du système d'exploitation sous-jacent est bidirectionnel ;

8. Buffer

Un tampon est essentiellement un bloc de mémoire qui peut lire et écrire des données. Il peut être compris comme un objet conteneur (y compris un tableau). Cet objet fournit un ensemble de méthodes qui peuvent faciliter son utilisation. blocs de mémoire, les objets tampon ont des mécanismes intégrés qui peuvent suivre et enregistrer les changements d'état du tampon. Channel fournit un canal pour lire les données des fichiers et des réseaux, mais les données lues ou écrites doivent passer par Buffer.
Dans NIO, Buffer est une classe parent de niveau supérieur, qui est une classe abstraite.

1. Liste des sous-classes de Buffer couramment utilisées

  • ByteBuffer, stocke les données d'octets dans le tampon ;

  • ShortBuffer, stocke les données de chaîne dans le tampon

  • CharBuffer, stocke les données de caractères dans le tampon ; Area ;

  • IntBuffer, stocke les données entières dans le tampon ;

  • LongBuffer, stocke les données entières longues dans le tampon 

  • DoubleBuffer, stocke les décimales dans le tampon ; des mals à le tampon ;

  • 2. Quatre attributs majeurs du tampon

mark : mark

  • position : position, l'index du prochain élément à lire ou à écrire, à chaque fois que les données du tampon sont lu ou écrit La valeur sera modifiée à chaque fois pour préparer la prochaine lecture et écriture.

  • limite : indique le point final actuel du tampon. Les opérations de lecture et d'écriture ne peuvent pas être effectuées sur les positions où le tampon dépasse la limite. Et la limite peut être modifiée

  • capacité : capacité, c'est-à-dire la quantité maximale de données pouvant être hébergées ; elle est définie lors de la création du tampon et ne peut pas être modifiée ;

3. L'API introduite lorsque le tampon est couramment utilisé apiAnalyser lutilisation et les caractéristiques des exemples Java NIO

JDK1.4

public final int capacité( )//Renvoyer la capacité de ce tampon

  • public final int position ( )//Renvoyer la position de ce tampon

  • public final Buffer position (int newPositio)//Définir la position de ce tampon

  • public final int limit( )//Renvoyer la limite de ce tampon

  • public final Buffer limit (int newLimit)//Définir la limite de ce tampon

  • public final Buffer mark( )//Définir une marque à la position de ce tampon

  • public final Buffer reset( ) // Réinitialise la position de ce tampon à la position de la marque précédente

  • public final Buffer clear( )//Effacer ce tampon, c'est-à-dire restaurer chaque marque à son état initial, mais les données ne sont pas réellement effacées, et sera écrasé par les opérations ultérieures

  • public final Buffer flip( )//Inverser ce tampon

  • public final Buffer rewind( )//Rembobiner ce tampon

  • public final int restant( )//Return la position actuelle avec Nombre d'éléments entre les limites

  • public final boolean hasRemaining( ) // Indique s'il y a des éléments entre la position actuelle et la limite

  • public abstract boolean isReadOnly( ); // Indique si ce tampon; est un Buffer en lecture seule

  • API introduite dans JDK1.6

public abstract boolean hasArray();//Informer si ce tampon a un tableau d'implémentation sous-jacent accessible

  • public abstract Object array();/ /Renvoie le tableau d'implémentation sous-jacent de ce tampon

  • public abstract int arrayOffset();//Renvoie le décalage du premier élément tampon dans le tableau d'implémentation sous-jacent de ce tampon

  • public abstract boolean isDirect( ; Lecture et l'écriture peut être effectuée en même temps, tandis que les flux ne peuvent que lire ou écrire ;

  • les canaux peuvent lire et écrire des données de manière asynchrone

les canaux peuvent lire les données du tampon ou écrire des données dans le tamponAnalyser lutilisation et les caractéristiques des exemples Java NIO

( 2 ) Le flux dans BIO est unidirectionnel. Par exemple, l'objet FileInputStream ne peut lire que des données, tandis que le canal dans NIO est bidirectionnel et peut lire ou écrire.

(3) Channel est une interface dans NIO (4) Les classes Channel couramment utilisées sont : FileChannel, DatagramChannel, ServerSocketChannel et SocketChannel. ServerSocketChanne est similaire à ServerSocket et SocketChannel est similaire à Socket. (5) FileChannel est utilisé pour la lecture et l'écriture de données de fichiers, DatagramChannel est utilisé pour la lecture et l'écriture de données UDP, ServerSocketChannel et SocketChannel sont utilisés pour la lecture et l'écriture de données TCP.

2. FileChannel
  • FileChannel est principalement utilisé pour effectuer des opérations d'E/S sur des fichiers locaux. Les méthodes courantes sont :

  • lire, lire les données du canal et les mettre dans le tampon

  • écrire, mettre le. buffer Les données de la zone sont écrites dans le canal

transferFrom, copiez les données du canal cible vers le canal actuel


transferTo, copiez les données du canal actuel vers le canal cible

3. Notes et détails sur Buffer et Channel

  • ByteBuffer prend en charge les types put et get. Quel que soit le type de données saisi, get doit utiliser le type de données correspondant pour le supprimer, sinon il peut y avoir une exception BufferUnderflowException. exception.

  • Vous pouvez convertir un Buffer normal en un Buffer en lecture seule.

  • NIO fournit également MappedByteBuffer, qui permet de modifier les fichiers directement en mémoire (mémoire en dehors du tas), et la synchronisation avec les fichiers est complétée par NIO.

  • NIO prend également en charge les opérations de lecture et d'écriture via plusieurs tampons (c'est-à-dire des tableaux de tampons), à savoir la diffusion et le rassemblement.

10. Sélecteur(sélecteur)

1. Introduction de base

  • NIO de Java, utilisant la méthode IO non bloquante. Vous pouvez utiliser un seul thread pour gérer plusieurs connexions client et vous utiliserez le sélecteur.

  • Selector peut détecter si un événement se produit sur plusieurs canaux enregistrés. Si un événement se produit, il obtiendra l'événement et traitera chaque événement en conséquence. De cette manière, un seul thread peut être utilisé pour gérer plusieurs canaux, c'est-à-dire pour gérer plusieurs connexions et requêtes.

  • Seulement lorsque la connexion/le canal a réellement un événement de lecture ou d'écriture, la lecture et l'écriture seront effectuées, ce qui réduit considérablement la surcharge du système, et il n'est pas nécessaire de créer un thread pour chaque connexion et de maintenir plusieurs threads.

  • Évitez la surcharge causée par le changement de contexte entre plusieurs threads.

2. Méthodes liées au sélecteur

  • open();//Obtenir un objet sélecteur

  • select(long timeout);//Surveiller tous les canaux enregistrés lorsqu'ils contiennent des opérations d'E/S Lorsque cela est possible , ajoutez la SelectionKey correspondante à la collection interne et renvoyez-la. Les paramètres sont utilisés pour définir le délai d'attente

  • selectedKeys( //Obtenir toutes les SelectionKeys de la collection interne.

3. Notes

La fonction ServerSocketChannel dans NIO est similaire à ServerSocket et la fonction SocketChannel est similaire à Socket.

11. Communication serveur-client simple via NIO

1. Serveur

package com.nezha.guor.nio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.util.Iterator;public class NioServer {
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private static final int PORT = 8080;

    public NioServer() {
        try {
            //获得选择器
            selector = Selector.open();
            serverSocketChannel =  ServerSocketChannel.open();
            //绑定端口
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            //设置非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //将该ServerSocketChannel 注册到selector
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }catch (IOException e) {
            System.out.println("NioServer error:"+e.getMessage());
        }
    }

    public void listen() {

        System.out.println("监听线程启动: " + Thread.currentThread().getName());
        try {
            while (true) {
                int count = selector.select();
                if(count > 0) {
                    //遍历得到selectionKey集合
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();

                        if(key.isAcceptable()) {
                            SocketChannel sc = serverSocketChannel.accept();
                            sc.configureBlocking(false);
                            sc.register(selector, SelectionKey.OP_READ);
                            System.out.println(sc.getRemoteAddress() + " 上线 ");
                        }
                        //通道发送read事件,即通道是可读的状态
                        if(key.isReadable()) {
                            getDataFromChannel(key);
                        }
                        //当前的key 删除,防止重复处理
                        iterator.remove();
                    }
                } else {
                    System.out.println("等待中");
                }
            }
        }catch (Exception e) {
            System.out.println("listen error:"+e.getMessage());
        }
    }

    private void getDataFromChannel(SelectionKey key) {
        SocketChannel channel = null;
        try {
            channel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            int count = channel.read(buffer);
            //根据count的值做处理
            if(count > 0) {
                String msg = new String(buffer.array());
                System.out.println("来自客户端: " + msg);

                //向其它的客户端转发消息(排除自己)
                sendInfoToOtherClients(msg, channel);
            }
        }catch (IOException e) {
            try {
                System.out.println(channel.getRemoteAddress() + " 离线了");
                //取消注册
                key.cancel();
            }catch (IOException ex) {
                System.out.println("getDataFromChannel error:"+ex.getMessage());
            }
        }finally {
            try {
                channel.close();
            }catch (IOException ex) {
                System.out.println("channel.close() error:"+ex.getMessage());
            }
        }
    }

    //转发消息给其它客户(通道)
    private void sendInfoToOtherClients(String msg, SocketChannel self ) throws  IOException{
        System.out.println("服务器转发消息中...");
        System.out.println("服务器转发数据给客户端线程: " + Thread.currentThread().getName());
        //遍历 所有注册到selector 上的 SocketChannel,并排除 self
        for(SelectionKey key: selector.keys()) {
            Channel targetChannel = key.channel();

            //排除自己
            if(targetChannel instanceof  SocketChannel && targetChannel != self) {
                SocketChannel dest = (SocketChannel)targetChannel;
                //将信息存储到buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                //将buffer数据写入通道
                dest.write(buffer);
            }
        }
    }

    public static void main(String[] args) {
        //创建服务器对象
        NioServer nioServer = new NioServer();
        nioServer.listen();
    }}

2. Client

package com.nezha.guor.nio;import 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.util.Iterator;import java.util.Scanner;public class NioClient {
    private final int PORT = 8080; //服务器端口
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    public NioClient() throws IOException {
        selector = Selector.open();
        socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //将channel注册到selector
        socketChannel.register(selector, SelectionKey.OP_READ);
        username = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println(username + " is ok...");
    }

    //向服务器发送消息
    public void sendInfo(String info) {
        info = username + " 说:" + info;
        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        }catch (IOException e) {
            System.out.println("sendInfo error:"+e.getMessage());
        }
    }

    //读取从服务器端回复的消息
    public void readInfo() {
        try {
            int readChannels = selector.select();
            if(readChannels > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    if(key.isReadable()) {
                        //得到相关的通道
                        SocketChannel sc = (SocketChannel) key.channel();
                        //得到一个Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //读取
                        sc.read(buffer);
                        //把读到的缓冲区的数据转成字符串
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                }
                iterator.remove(); //删除当前的selectionKey, 防止重复操作
            } else {
                System.out.println("没有可以用的通道...");
            }
        }catch (Exception e) {
            System.out.println("readInfo error:"+e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        NioClient nioClient = new NioClient();
        new Thread() {
            public void run() {
                while (true) {
                    nioClient.readInfo();
                    try {
                        Thread.currentThread().sleep(2000);
                    }catch (InterruptedException e) {
                        System.out.println("sleep error:"+e.getMessage());
                    }
                }
            }
        }.start();

        //发送数据给服务器端
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNextLine()) {
            nioClient.sendInfo(scanner.nextLine());
        }
    }}

3.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer