Distributed topic|Heart this article, I am no longer afraid of the interviewer asking BIO, NIO, AIO, I am the first, you are free

The IO model refers to what channel is used to send and receive data in the process of network data transmission. Our common ones are BIO, NIO, AIO (NIO2.0), and I will introduce these in detail next

What does synchronous/asynchronous/blocking/non-blocking mean?

  • Synchronous/asynchronous
    means that you call a method. If the method is synchronous, then you will wait for the execution of this method to finish before performing subsequent operations;
    if it is asynchronous, it will return to you immediately, but this is not true The real result is notified to you through the message mechanism or the callback mechanism.

  • Blocking/Non-blocking
    Blocking refers to when you call a method to obtain washing machine information. If there is no washing machine at this time, the method will keep blocking until the washing machine information can be queried. The result will not be returned;
    non-blocking refers to When you call a method for obtaining washing machine information, if the information is not found at the time, you will not be blocked there forever. You can do other things at this time, but you will check whether there are results from time to time, and then Block acquisition, but this will not affect you to do other things.

BIO (synchronous blocking)

We often use BIO. When we learn programming basics javaSE, everyone should have learned socket communication. The synchronous blocking is used here.
Let's first look at the BIO model:

Insert picture description here

In the BIO model, a connection corresponds to a processing thread. If the server uses a single thread for processing, subsequent connections will always be blocked;

  • Disadvantages:
    • The read operation in the code is a blocking operation. If the server does not send data after connection, it will always block the current thread and waste resources.
    • If the number of connections is large, it means that the number of threads created will increase, which will cause too much pressure on the server. The subsequent optimization into a thread pool processing method barely solves this problem.
  • Application scenario
    BIO is suitable for a fixed architecture with a small number of connections. This mode requires higher server resources, but the program complexity is relatively low;

Code example

// 客户端
package com.example.netty.bio;

import java.io.IOException;
import java.net.Socket;

public class SocketClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",9000);
        socket.getOutputStream().write("我是客户端".getBytes());
        socket.getOutputStream().flush();
        System.out.println("向服务端发送数据结束");
        byte[] bytes = new byte[1024];
        try {
            int read = socket.getInputStream().read(bytes);
            System.out.println("服务端发送过来的数据为:"+new String(bytes,0,read));
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

        }

    }
}
 //服务端
 package com.example.netty.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServer{
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket=new ServerSocket(9000);
        while (true){
            System.out.println("等待连接");
            // 阻塞等待
            Socket client = serverSocket.accept();
            System.out.println("有客户端连接了");
            handleRead(client);
        }
    }

    /**
     *
     * @param client
     */
    private static void handleRead(Socket client) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                byte[] bytes = new byte[1024];
                try {
                    int read = client.getInputStream().read(bytes);
                    System.out.println("客户端发送过来的数据为:"+new String(bytes,0,read));
//                    Thread.sleep(Integer.MAX_VALUE);
                    client.getOutputStream().write("你好,我收到你的数据了".getBytes());
                    client.getOutputStream().flush();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {

                }
            }
        }).start();

    }
}

NIO (synchronous non-blocking)

NIO has been upgraded on the basis of BIO, replacing blocking with non-blocking. Although only the mode has changed, the amount of code complexity has increased a lot.
In the NIO model, the server can open a thread to handle multiple connections. It is non-blocking. The data sent by the client will be registered on the multiplexer selector. When the selector (selector's select method is blocking) polling When there is a read, write, or connection request, it will be forwarded to the back-end program for processing. When there is no data, the business program does not need to block waiting.

NIO has three major components: Channel (channel), Buffer (buffer area), Selector (selector)

  • Channel is
    similar to a stream, but it is a two-way stream. It is a channel connecting the server and the client. Both the client and the server can use the channel to read and write data.
  • Buffer
    is a buffer used to store data. It uses channels to transfer data between the client and the server.
  • Selector
    corresponds to one or more threads, the client connection will be registered on the selector, and then the selector will call the back-end handler

NIO structure model

Insert picture description here

Where is the non-blocking of NIO?

Looking at the model diagram, everyone may know that all connection channels of the client will be registered on the selector, and select will obtain the status of these channels through polling. These statuses include accpet (connection request) and READ read request.

If it is found that there is already a connection request status during the polling process, it means that there is already a client who wants to connect with the server, and directly pass this channel to the back-end program to process the connection operation; if it is in the BIO model If it is, it will always be blocked on accept and will not be released until there is a connection request, and the subsequent code will continue to be executed.

If a read request status is found during the polling process, it means that a client has already sent data to the server, and the server can directly pass the channel to the back-end program to process the read operation; if it is in Under the BIO model, the read will be blocked until there is a connection request, and the subsequent code will continue to be executed.

The above can be summarized as: In the NIO model, if the server executes the read operation, it means that there is already available data to read. If the accpet operation is executed, it means that the client must initiate and service at this time. The connection of the end can ensure that the premise of this operation is that the selector polls the readable and connectable channel state.

If the selector polls the read and accpet status of multiple channels, how does NIO deal with it?


  • If single- threaded in single-threaded mode, the processing method is based on the order of the changed channels obtained after polling. Yes, it is processed synchronously, that is, it can only process the operation of this channel. In order to continue processing the request of the next channel, in the selector code, it is processed by traversing all the changed channels. After reading the code, you will understand that this mode of one selector corresponding to one thread is actually the single-threaded IO of redis Model .


  • If multithreading is in multithreading mode, when the selector traverses each channel, it will hand over the operation of the channel to a thread for processing, and generally will be thrown into the thread for processing. At this time, the order of execution depends on the cpu The scheduling situation is up to you.

Next, we combine the code to look at the working mechanism of NIO as a whole

Code example

  • Server code
package com.example.netty.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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(9000));

        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            System.out.println("等待事件发生");
            // 这里还是阻塞等待,
            int select = selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                handleChannel(selectionKey);
            }
        }
    }

    private static void handleChannel(SelectionKey selectionKey) {
        if (selectionKey.isAcceptable()) {
            System.out.println("有客户端发生了连接");
            ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
            try {
                SocketChannel client = channel.accept();
                client.configureBlocking(false);
                // 连接之后立即注册读操作,客户端发送数据过来才能监听到
                client.register(selectionKey.selector(), SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if (selectionKey.isReadable()) {

            System.out.println("收到客户端发送数据的请求");
            SocketChannel channel = (SocketChannel) selectionKey.channel();

            // 如果这里你不读取数据,读事件会一直触发,这是有NIO属于水平触发决定的,
            ByteBuffer allocate = ByteBuffer.allocate(1024);
            try {
                int read = channel.read(allocate);
                if (read != -1) {
                    System.out.println("客户端发送的数据:" + new String(allocate.array(), 0, read));
                }
                channel.write(ByteBuffer.wrap("你好,我是服务端".getBytes()));
                // 处理完读操作之后,需要重新注册下读操作,
                // 如果下面一行被放开,将会一直会有可写操作触发,因为网络中99.999%的情况下都是可写的,一般不监听
//                selectionKey.interestOps(SelectionKey.OP_WRITE | SelectionKey.OP_READ);
                selectionKey.interestOps(SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if (selectionKey.isWritable()) {
            System.out.println("触发写事件");
        }
    }
}

  • Server side architecture diagram

Insert picture description here

  • Server code description

    1. Create a ServerSocketChannel and Selector, and register the ServerSocketChannel to the Selector
    2. The selector monitors the channel event through the select() method. When the client connects, the selector monitors the connection event and obtains the selectionKey bound to the ServerSocketChannel registration.
    3. selectionKey can get the bound ServerSocketChannel through the channel() method
    4. ServerSocketChannel gets SocketChannel through accept() method
    5. Register the SocketChannel to the Selector and care about the read event
    6. Return a SelectionKey after registration, which will be associated with the SocketChannel
    7. The selector continues to monitor events through the select() method. When the client sends data to the server, the selector listens to the read event and obtains the selectionKey bound to the SocketChannel registration.
    8. selectionKey can get the bound socketChannel through the channel() method
    9. Read the data in the socketChannel
    10. Use socketChannel to write server data back to the client

    You may have noticed the horizontal trigger I mentioned in the code comments. I will explain this: horizontal trigger is a mode of multiplexing, and there is an edge trigger corresponding to this.

  • Horizontal trigger

    If a notification is triggered after a data change is detected in the channel, if the event handler does not read all the data in the buffer completely or not at all after receiving the notification, then in the horizontal trigger mode, this will always be triggered Notice, until the contents of the buffer are read, select and poll in NIO belong to this mode

  • Edge trigger

    The situation is the same as above, but after the system has notified once, the notification will occur again only when the data in the channel changes again. Epoll can use either horizontal trigger or edge trigger.

  • Client code

package com.example.netty.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;

public class NioClient {
    private Selector selector;

    public static void main(String[] args) throws IOException {
        NioClient client = new NioClient();
        client.initClient("127.0.0.1", 9000);
        client.connect();
    }

    private void connect() throws IOException {
        while (true) {
            // 阻塞等待
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                handler(selectionKey);
            }
        }
    }

    private void handler(SelectionKey selectionKey) throws IOException {
        // 收到服务端发送的数据
        if (selectionKey.isReadable()) {
            SocketChannel channel = (SocketChannel) selectionKey.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = channel.read(buffer);
            if (len != -1) {
                System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
            }
        // 连接事件发生
        } else if (selectionKey.isConnectable()) {
            SocketChannel channel = (SocketChannel) selectionKey.channel();
            // 一般发起了连接后,会立即返回,需要使用isConnectionPending判断是否完成连接,如果正在连接,则调用finishConnect,如果不能连接则会抛出异常
            if (channel.isConnectionPending()) {
                channel.finishConnect();
            }
            channel.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.wrap("你好,我是客户端".getBytes());
            channel.write(buffer);
            selectionKey.interestOps(SelectionKey.OP_READ);
        }
    }

    private void initClient(String s, int i) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        selector = Selector.open();
        socketChannel.connect(new InetSocketAddress(s, i));
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
    }
}

Multiplexing common underlying implementation api

Insert picture description here

Compared with selelct, poll has no maximum connection limit;
epoll is a different mechanism than the former two, which notifies the caller based on event notification;

AIO (asynchronous non-blocking)

Asynchronous and non-blocking. After the operating system is completed, the server program is called back to notify the server program to start the thread to process. It is generally suitable for
application scenarios with a large number of connections and a long connection time : AIO mode is suitable for a large number of connections and a relatively long connection (re-operation ) Architecture, JDK7 began to support

  • AIO code example:
    // 服务端代码
    package com.example.netty.aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AioServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        final AsynchronousServerSocketChannel serverChannel =
                AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));
        serverChannel.accept(null,new  CompletionHandler<AsynchronousSocketChannel, Object>() {

            @Override
            public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
                serverChannel.accept(attachment, this);
                try {
                    System.out.println(socketChannel.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {

                        @Override
                        public void completed(Integer result, ByteBuffer attachment) {
                            buffer.flip();
                            System.out.println(new String(buffer.array(), 0, result));
                            socketChannel.write(ByteBuffer.wrap("HelloAioClient".getBytes()));
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {

                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, Object attachment) {

            }
        });
        Thread.sleep(Integer.MAX_VALUE);
    }
}

// 客户端代码
package com.example.netty.aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;

public class AioClient {
    public static void main(String[] args) throws ExecutionException, InterruptedException, IOException {
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get();
        socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
        ByteBuffer buffer = ByteBuffer.allocate(512);
        Integer len = socketChannel.read(buffer).get();
        if (len!=-1) {
            System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
        }
    }
}

Summary: It can be seen that the code of asynchronous non-blocking mode is very simple. All operations are triggered by the callback mechanism. We only need to process our own logic in the callback method. In fact, AIO is encapsulated based on NIO. As we will talk about later, netty is also encapsulated based on NIO.

BIO, NIO, AIO division

Insert picture description here

Search on WeChat [AI Coder] Follow the handsome me and reply [Receive dry goods], there will be a lot of interview materials and architect must-read books waiting for you to choose, including java basics, java concurrency, microservices, middleware, etc. More information is waiting for you.

Guess you like

Origin blog.csdn.net/weixin_34311210/article/details/111089399