Serie IO (1) Explicación básica detallada del modelo IO

Tabla de contenido

1. BIO (Bloqueo de E / S)

Escenarios de aplicación:

1.2 NIO (E / S sin bloqueo)

Escenarios de aplicación:

para resumir:

3. AIO (NIO 2.0)

4. Comparación de BIO, NIO, AIO:

para resumir:


Java admite 3 modos IO de programación de red: BIO, NIO, AIO; el modelo IO significa qué tipo de canal se utiliza para enviar y recibir datos,

1. BIO (Bloqueo de E / S)

Modelo de bloqueo síncrono, una conexión de cliente corresponde a un subproceso de procesamiento, el modelo de E / S más popular es el modelo de E / S de prueba de bloqueo; todos los sockets están bloqueados por defecto, tome el socket del datagrama como ejemplo

Como se muestra en la figura: el proceso llama a recvfrom, y su llamada al sistema no regresa hasta que llega el datagrama y se copia al búfer del proceso de aplicación o se produce un error.

Desventajas:

1. La operación de lectura en el código IO es una operación de bloqueo. Si la conexión no realiza operaciones de lectura y escritura de datos, hará que el hilo se bloquee y desperdicie recursos.

2. Si hay demasiados subprocesos, provocará demasiados subprocesos del servidor y demasiada presión.

Escenarios de aplicación:

El método BIO es adecuado para una arquitectura con un número relativamente pequeño de conexiones y un número fijo de conexiones Este método requiere mayores recursos del servidor, pero el programa es simple y fácil de entender.

Ejemplo de código BIO: https://github.com/ssy-githup/io-mode bajo   el paquete bio

//服务端代码:
/**
 * bio模式下的服务端
 */
public class SocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            System.out.println("等待连接。。");
            //阻塞方法
            final Socket socket = serverSocket.accept();
            System.out.println("有客户端连接了。。");
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        handler(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            //handler(socket);

        }
    }
    private static void handler(Socket socket) throws IOException {
        System.out.println("当前线程ID= " + Thread.currentThread().getId());
        byte[] bytes = new byte[1024];

        System.out.println("准备read。。");
        //接收客户端的数据,阻塞方法,没有数据可读时就阻塞
        int read = socket.getInputStream().read(bytes);
        System.out.println("read完毕。。");
        if (read != -1) {
            System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
            System.out.println("当前线程ID = " + Thread.currentThread().getId());

        }
        socket.getOutputStream().write("HelloClient".getBytes());
        socket.getOutputStream().flush();
    }
}

Codigo del cliente:

/**
 * bio 客户端
 */
public class SocketClient {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 9000);
        //向服务端发送数据
        socket.getOutputStream().write("HelloServer".getBytes());
        socket.getOutputStream().flush();
        System.out.println("向服务端发送数据结束");
        byte[] bytes = new byte[1024];
        //接收服务端回传的数据
        socket.getInputStream().read(bytes);
        System.out.println("接收到服务端的数据:" + new String(bytes));
        socket.close();
    }
}

1.2 NIO (E / S sin bloqueo)

El proceso de configurar un socket para que no bloquee es notificar al kernel: cuando todas las operaciones de E / S solicitadas tienen que poner el proceso en suspensión para completarse, no ponga el proceso en suspensión, pero devuelva un error.

Sin bloqueo sincrónico, el modo de implementación del servidor es que un hilo puede manejar múltiples solicitudes (conexiones), las solicitudes de conexión enviadas por el cliente se registrarán en el selector de multiplexor y el multiplexor sondea hasta que haya una solicitud de IO conectada. La capa inferior de multiplexación de E / S generalmente se implementa mediante API de Linux (select, poll, epoll) y sus diferencias son las siguientes:

  Seleccione encuesta epoll (jdk 1.5 y superior)

Método de operación

atravesar

atravesar

Llamar de vuelta

Implementación de bajo nivel

Formación

Lista enlazada

Tabla de picadillo

Eficiencia IO

Cada llamada se atraviesa linealmente y la complejidad del tiempo es O (n)

Cada llamada se atraviesa linealmente y la complejidad del tiempo es O (n)

Método de notificación de eventos, siempre que un evento IO esté listo, se llamará a la función de devolución de llamada registrada por el sistema y la complejidad del tiempo es O (1)

Conexión máxima

Tapado

ilimitado

ilimitado

Escenarios de aplicación:

El método NIO es adecuado para arquitecturas con una gran cantidad de conexiones y conexiones relativamente cortas (operación ligera), como servidores de chat, sistemas de barrera, comunicación entre servidores y la programación es más complicada. JDK1.4 comenzó a admitir

NIO tiene tres componentes principales: canal (canal), búfer (búfer), selector (selector)

1. Un canal es similar a una secuencia, cada canal corresponde a un búfer de búfer y la capa inferior del búfer es una matriz.

2. El canal se registrará en el selector y el selector lo entregará a un subproceso inactivo para su procesamiento de acuerdo con la ocurrencia de eventos de lectura y escritura del canal.

3. El selector puede corresponder a uno o más subprocesos

4. El búfer y el canal de NIO se pueden leer y escribir

Ejemplo de código NIO: código de servidor

public class NIOServer {


    public static void main(String[] args) throws IOException {
        // 创建一个在本地端口进行监听的服务Socket通道.并设置为非阻塞方式
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //必须配置为非阻塞才能往selector上注册,否则会报错,selector模式本身就是非阻塞模式
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(8888));
        // 创建一个选择器selector
        Selector selector = Selector.open();
        // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            System.out.println("等待事件发生。。");
            // 轮询监听channel里的key,select是阻塞的,accept()也是阻塞的
            int select = selector.select();

            System.out.println("有事件发生了。。");
            // 有客户端请求,被轮询监听到
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //删除本次已处理的key,防止下次select重复处理
                it.remove();
                handle(key);
            }
        }
    }

    private static void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            System.out.println("有客户端连接事件发生了。。");
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //NIO非阻塞体现:此处accept方法是阻塞的,但是这里因为是发生了连接事件,所以这个方法会马上执行完,不会阻塞
            //处理完连接请求不会继续等待客户端的数据发送
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            //通过Selector监听Channel时对读事件感兴趣
            sc.register(key.selector(), SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            System.out.println("有客户端数据可读事件发生了。。");
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //NIO非阻塞体现:首先read方法不会阻塞,其次这种事件响应模型,当调用到read方法时肯定是发生了客户端发送数据的事件
            int len = sc.read(buffer);
            if (len != -1) {
                System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
            sc.write(bufferToWrite);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        } else if (key.isWritable()) {
            SocketChannel sc = (SocketChannel) key.channel();
            System.out.println("write事件");
            // NIO事件触发是水平触发
            // 使用Java的NIO编程的时候,在没有数据可以往外写的时候要取消写事件,
            // 在有数据往外写的时候再注册写事件
            key.interestOps(SelectionKey.OP_READ);
            //sc.close();
        }
    }
}

Codigo del cliente:

public class NioClient {
    //通道管理器
    private Selector selector;

    /**
     * 启动客户端测试
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NioClient client = new NioClient();
        client.initClient("127.0.0.1", 8888);
        client.connect();
    }

    /**
     * 获得一个Socket通道,并对该通道做一些初始化的工作
     *
     * @param ip   连接的服务器的ip
     * @param port 连接的服务器的端口号
     * @throws IOException
     */
    public void initClient(String ip, int port) throws IOException {
        // 获得一个Socket通道
        SocketChannel channel = SocketChannel.open();
        // 设置通道为非阻塞
        channel.configureBlocking(false);
        // 获得一个通道管理器
        this.selector = Selector.open();

        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
        //用channel.finishConnect() 才能完成连接
        channel.connect(new InetSocketAddress(ip, port));
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     *
     * @throws IOException
     */
    public void connect() throws IOException {
        // 轮询访问selector
        while (true) {
            selector.select();
            // 获得selector中选中的项的迭代器
            Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                // 删除已选的key,以防重复处理
                it.remove();
                // 连接事件发生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    // 如果正在连接,则完成连接
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();
                    }
                    // 设置成非阻塞
                    channel.configureBlocking(false);
                    //在这里可以给服务端发送信息哦
                    ByteBuffer buffer = ByteBuffer.wrap("HelloServer".getBytes());
                    channel.write(buffer);
                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);                                            // 获得了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }
            }
        }
    }

    /**
     * 处理读取服务端发来的信息 的事件
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        //和服务端的read方法一样
        // 服务器可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = channel.read(buffer);
        if (len != -1) {
            System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
        }
    }
}

Flujo de código del servidor NIO:

  1. Cree un ServerSocketChannel y un Selector, y registre ServerSocketChannel en el Selector
  2. El selector monitorea el evento del canal a través del método select (). Cuando el cliente se conecta, el selector monitorea el evento de conexión y obtiene el límite de selección cuando se registra ServerSocketChannel.
  3. selectionKey puede obtener el ServerSocketChannel vinculado a través del método channel ()
  4. ServerSocketChannel obtiene SocketChannel a través del método accept ()
  5. Registre el SocketChannel en el Selector y preocúpese por el evento de lectura
  6. Devuelve una SelectionKey después del registro, que se asociará con SocketChannel
  7. El selector continúa monitoreando eventos a través del método select (). Cuando el cliente envía datos al servidor, el selector escucha el evento de lectura y obtiene el límite de selectionKey cuando el SocketChannel está registrado.
  8. selectionKey puede obtener el socketChannel vinculado a través del método channel ()
  9. Leer los datos en el socketChannel
  10. Utilice socketChannel para volver a escribir los datos del servidor en el cliente

para resumir:

El selector del modelo NIO es como un gran administrador, responsable de monitorear varios eventos de IO y luego reenviarlos al subproceso de back-end para su procesamiento. La manifestación de no bloqueo de NIO en relación con BIO es que el subproceso de back-end de BIO debe bloquearse esperando que el cliente escriba datos (como el método de lectura). Si el cliente no escribe datos, el subproceso será bloqueado, y NIO entregará la espera para que el cliente opere. El selector es responsable de sondear a todos los clientes registrados, y solo se transfiere al subproceso de back-end para su procesamiento cuando se ha producido un evento. no es necesario realizar ningún bloqueo en espera, y puede procesar directamente los datos del evento del cliente. Finaliza inmediatamente después del final, o vuelve al grupo de subprocesos para que otros eventos del cliente continúen utilizándose. También hay canales de lectura y escritura sin bloqueo.

3. AIO (NIO 2.0)

Asíncrono y sin bloqueo. Una vez completado el sistema operativo, se vuelve a llamar al programa del servidor para notificar al programa del servidor que inicie el proceso de subproceso. Por lo general, es adecuado para aplicaciones con una gran cantidad de conexiones y un tiempo de conexión prolongado.

Escenario de aplicación: el método AIO es adecuado para arquitecturas con una gran cantidad de conexiones y una conexión relativamente larga (operación pesada). JDK7 comenzó a admitir

4. Comparación de BIO, NIO, AIO:

  BIO NIO AIO
Modelo IO Bloqueo sincrónico Sin bloqueo síncrono Sin bloqueo asincrónico
Fácil de codificar sencillo complejo complejo
fiabilidad diferencia Genial Genial
Rendimiento bajo elevado elevado

para resumir:

 

 

Supongo que te gusta

Origin blog.csdn.net/qq_38130094/article/details/104721833
Recomendado
Clasificación