IO series (1) IO model basic detailed explanation

table of Contents

1. BIO(Blocking IO)

Application scenarios:

1.2 NIO (Non Blocking IO)

Application scenarios:

to sum up:

3. AIO (NIO 2.0)

4. Comparison of BIO, NIO, AIO:

to sum up:


Java supports 3 network programming IO modes: BIO, NIO, AIO; the IO model means what kind of channel is used to send and receive data,

1. BIO(Blocking IO)

Synchronous blocking model, a client connection corresponds to a processing thread, the most popular I/O model is the blocking trial I/O model; all sockets are blocked by default, take the datagram socket as an example

As shown in the figure: the process calls recvfrom, and its system call does not return until the datagram arrives and is copied to the buffer of the application process or an error occurs.

Disadvantages:

1. The read operation in the IO code is a blocking operation. If the connection does not perform data read and write operations, it will cause the thread to block and waste resources.

2. If there are too many threads, it will cause too many server threads and too much pressure.

Application scenarios:

The BIO method is suitable for architectures with a relatively small and fixed number of connections. This method requires relatively high server resources, but the program is simple and easy to understand.

BIO code example: https://github.com/ssy-githup/io-mode under   the bio package

//服务端代码:
/**
 * 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();
    }
}

Client code:

/**
 * 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 (Non Blocking IO)

The process of setting a socket to non-blocking is notifying the kernel: when all requested I/O operations have to put the process to sleep to complete, do not put the process to sleep, but return an error.

Synchronous non-blocking, the server implementation mode is that one thread can handle multiple requests (connections), the connection requests sent by the client will be registered on the multiplexer selector, and the multiplexer polls until there is an IO request connected deal with. The bottom layer of I/O multiplexing is generally implemented using Linux APIs (select, poll, epoll), and their differences are as follows:

  select poll epoll (jdk 1.5 and above)

Operation method

Traverse

Traverse

Callback

Low-level implementation

Array

Linked list

Hash table

IO efficiency

Each call is linearly traversed, and the time complexity is O(n)

Each call is linearly traversed, and the time complexity is O(n)

Event notification method, whenever an IO event is ready, the callback function registered by the system will be called, and the time complexity is O(1)

Maximum connection

Capped

unlimited

unlimited

Application scenarios:

The NIO method is suitable for architectures with a large number of connections and relatively short connections (light operation), such as chat servers, barrage systems, inter-server communication, and programming is more complicated. JDK1.4 began to support

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

1. A channel is similar to a stream, each channel corresponds to a buffer buffer, and the bottom layer of the buffer is an array

2. The channel will be registered on the selector, and the selector will hand it over to an idle thread for processing according to the occurrence of channel read and write events

3. The selector can correspond to one or more threads

4. NIO's Buffer and channel can both be read and written

NIO code example: server code

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();
        }
    }
}

Client code:

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));
        }
    }
}

NIO server code flow:

  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 when the ServerSocketChannel is registered.
  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 when the SocketChannel is registered.
  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

to sum up:

The selector of the NIO model is like a big manager, responsible for monitoring various IO events, and then forwarding them to the back-end thread for processing. The non-blocking manifestation of NIO relative to BIO is that the back-end thread of BIO needs to be blocked waiting for the client to write data (such as the read method). If the client does not write data, the thread will block, and NIO will hand over the waiting for the client to operate. The selector is responsible for polling all registered clients, and it is only transferred to the back-end thread for processing when an event has occurred. The back-end thread does not need to do any blocking waiting, and can directly process the data of the client event. End immediately after the end, or return to the thread pool for other client events to continue to use. There is also channel read and write is non-blocking

3. AIO (NIO 2.0)

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 applications with a large number of connections and a long connection time.

Application scenario: The AIO method is suitable for architectures with a large number of connections and relatively long connections (heavy operations). JDK7 began to support

4. Comparison of BIO, NIO, AIO:

  BIO NIO AIO
IO model Synchronous blocking Synchronous non-blocking Asynchronous non-blocking
Easy to code simple complex complex
reliability difference Great Great
Throughput low high high

to sum up:

 

 

Guess you like

Origin blog.csdn.net/qq_38130094/article/details/104721833