java architecture road - (netty topic) preliminary understanding of BIO, NIO, AIO

Keywords: socket Java Netty Programming

This time, we mainly talk about our IO blocking model. It's just not much, but we must understand that it's very important for us to understand netty later

IO model elaboration

IO model is to use what kind of channel to send and receive data. Java supports three kinds of network programming IO modes: BIO, NIO, AIO.

BIO

BIO(Blocking IO) synchronous blocking model, one client connection corresponds to one processing thread. We are also familiar with the synchronous blocking model. Let's not worry about the concept of synchronization. Let's take a look at what blocking is. Let's take a brief code.

Server:

package com.xiaocai.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("waiting for connection." );
//Blocking method
Socket socket = serverSocket.accept();
System.out.println("there is a client connection." );
handler(socket);
}
}

private static void handler(Socket socket) throws IOException {
    System.out.println("thread id = " + Thread.currentThread().getId());
    byte[] bytes = new byte[1024];

    System.out.println("Get ready read. . ");
    //Receive client data, block method, block when no data is readable
    int read = socket.getInputStream().read(bytes);
    System.out.println("read Complete..");
    if (read != -1) {
        System.out.println("Data received from client:" + new String(bytes, 0, read));
        System.out.println("thread id = " + Thread.currentThread().getId());

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

}
Client

package com.xiaocai.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);
    //Send data to server
    socket.getOutputStream().write("HelloServer".getBytes());
    socket.getOutputStream().flush();
    System.out.println("End of sending data to server");
    byte[] bytes = new byte[1024];
    //Receive the data returned by the server
    socket.getInputStream().read(bytes);
    System.out.println("Data received from the server:" + new String(bytes));
    socket.close();
}

}
This is a simple BIO server code, which is the code segment to be accessed by the thread. What does this single threaded version mean?

We start a socket service with a port of 9000, and then run Socket socket = serverSocket.accept(); that is to say, wait for the thread to appear, and we will receive the client's request. This method is blocked, and only in the blocked state can we receive our request. When there is a request coming in, run the handler(socket); method, in the middle is the method of printing thread ID, which is not explained, int read = socket.getInputStream().read(bytes); ready to read the data sent by our client. Read and write may be confused. Let me draw a picture to illustrate.

We can also see that our client first gets the socket connection (Socket socket = new Socket("127.0.0.1", 9000)), and then writes the data to the server (socket.getOutputStream().write("HelloServer". getBytes());) in byte byte bytes. At this time, our server waits for the read data of our client weite, and it will enter the blocking state. If our client does not write the data, our client will always be in the blocking state, and it will not receive new requests. Because of the blocking, it cannot return to our Socket socket = serverSocket.accept(); to wait for the client's requests, as long as it is in serverSocket.accept() New requests can only be received when blocked. So we took a multi-threaded approach to solve this problem. Let's take a look at the code.

package com.xiaocai.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("waiting for connection." );
//Blocking method
Socket socket = serverSocket.accept();
System.out.println("there is a client connection." );
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}

private static void handler(Socket socket) throws IOException {
    System.out.println("thread id = " + Thread.currentThread().getId());
    byte[] bytes = new byte[1024];

    System.out.println("Get ready read. . ");
    //Receive client data, block method, block when no data is readable
    int read = socket.getInputStream().read(bytes);
    System.out.println("read Complete..");
    if (read != -1) {
        System.out.println("Data received from client:" + new String(bytes, 0, read));
        System.out.println("thread id = " + Thread.currentThread().getId());

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

}
At this time, every time a client comes to a new request, we will start a thread to process the request. In time, your client does not have timely write data. Although our server read is blocked, it just blocks your own thread, which will not cause other requests to be unreachable.

This seems to be a lot better. In fact, if you want to take an example, when we watch the live broadcast of our little sister, you are welcome to list one big brother. There are many bullet screens. It's OK to join 100 bullet screens at one time. Let's start 100 threads to deal with it. What about 100000 bullet screens together? Are you going to start 100000 threads to handle these barrages? Obviously, there are disadvantages in BIO, but there are advantages in BIO (less code, not easy to make mistakes).

NIO

NIO(Non Blocking IO) is synchronous and non blocking. The implementation mode of the server is that one thread can handle multiple requests (connections). The connection requests sent by the client will be registered on the multiplexer selector. When the multiplexer polls the connection and has IO requests, it will be processed. Maybe the concept is too abstract. Let me give you an example. Now there are two communities with many houses for rent. BIO community and NIO community have a guard. BIO community has a tenant. The guard master will take the key and take the tenant to see the house. The tenants in the back can't see the house for the time being. It's embarrassing If you want to see more people's houses at the same time, you have to increase the number of doorkeepers. Our NIO community is very smart. It's still a doorkeeper's mother. When a renter comes to see the house, the doorkeeper's mother gives the renter a key and tells him which room is empty. You can go in and have a look at it yourself. In time, the renter's house watching is slow and takes a lot of time, because the doorkeeper is not afraid of it Aunt has always been in the guard room. Even if there are new tenants, it's the same with aunt. Just give the key and the address of the empty room. I remember this example very clearly, and I think it's very appropriate. Here I mentioned the concept of a key. I'll tell you what it is. Let's take a look at the code first.

Server side

package com.xiaocai.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 ExecutorService pool = Executors.newFixedThreadPool(10);

public static void main(String[] args) throws IOException {
    // Create a service Socket channel to listen on the local port and set it to non blocking mode
    ServerSocketChannel ssc = ServerSocketChannel.open();
    //It must be configured as non blocking to register on the selector, otherwise an error will be reported. The selector mode itself is non blocking mode
    ssc.configureBlocking(false);
    ssc.socket().bind(new InetSocketAddress(9000));
    // Create a selector
    Selector selector = Selector.open();
    // Register the ServerSocketChannel to the selector, and the selector is interested in the client accept connection operation
    ssc.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
        System.out.println("Wait for the event to occur..");
        // Polling the key in the monitoring channel, select is blocked, and accept() is also blocked
        int select = selector.select();

        System.out.println("Something happened..");
        // There are client requests that are polled and monitored
        Iterator<SelectionKey> it = selector.selectedKeys().iterator();
        while (it.hasNext()) {
            SelectionKey key = it.next();
            //Delete the key processed this time to prevent the next select from being processed repeatedly
            it.remove();
            handle(key);
        }
    }
}

private static void handle(SelectionKey key) throws IOException {
    if (key.isAcceptable()) {
        System.out.println("A client connection event occurred..");
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        //NIO non blocking embodiment: the accept method is blocked here, but because of the connection event, the method will be executed immediately and will not block
        //After processing the connection request, it will not wait for the client to send data
        SocketChannel sc = ssc.accept();
        sc.configureBlocking(false);
        //Interested in reading events when listening to Channel through Selector
        sc.register(key.selector(), SelectionKey.OP_READ);
    } else if (key.isReadable()) {
        System.out.println("A client data readable event occurred..");
        SocketChannel sc = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //NIO non blocking embodiment: first, read method will not block, second, this event response model, when the read method is called, there must be an event that the client sends data
        int len = sc.read(buffer);
        if (len != -1) {
            System.out.println("Read to data sent by client:" + 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 Event");
        // NIO event trigger is horizontal trigger
        // When using Java NIO programming, you need to cancel writing events when there is no data to write out,
        // Register to write events when data is written out
        key.interestOps(SelectionKey.OP_READ);
        //sc.close();
    }
}

}
Client

package com.xiaocai.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 {
//Channel Manager
private Selector selector;

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

/**
 * Obtain a Socket channel, and do some initialization work for the channel
 *
 * @param ip   ip of the connected server
 * @param port Port number of the connected server
 * @throws IOException
 */
public void initClient(String ip, int port) throws IOException {
    // Get a Socket channel
    SocketChannel channel = SocketChannel.open();
    // Set channel to non blocking
    channel.configureBlocking(false);
    // Get a channel manager
    this.selector = Selector.open();

    // The client connects to the server. In fact, the implementation of the method does not realize the connection. It needs to be adjusted in the listen () method.
    //Use channel.finishConnect() to complete the connection
    channel.connect(new InetSocketAddress(ip, port));
    //Bind the channel manager to the channel and register the selectionkey.op'connect event for the channel.
    channel.register(selector, SelectionKey.OP_CONNECT);
}

/**
 * Use polling to monitor whether there are events to be processed on the selector. If so, process them
 *
 * @throws IOException
 */
public void connect() throws IOException {
    // Polling access selector
    while (true) {
        selector.select();
        // Get the iterator of the selected item in the selector
        Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
        while (it.hasNext()) {
            SelectionKey key = (SelectionKey) it.next();
            // Delete the selected key to prevent duplicate processing
            it.remove();
            // Connection event occurred
            if (key.isConnectable()) {
                SocketChannel channel = (SocketChannel) key.channel();
                // If connecting, complete the connection
                if (channel.isConnectionPending()) {
                    channel.finishConnect();
                }
                // Set to non blocking
                channel.configureBlocking(false);
                //You can send information to the server here
                ByteBuffer buffer = ByteBuffer.wrap("HelloServer".getBytes());
                channel.write(buffer);
                //After the connection with the server is successful, in order to receive the information of the server, you need to set the read permission for the channel.
                channel.register(this.selector, SelectionKey.OP_READ);                                            // Get readable events
            } else if (key.isReadable()) {
                read(key);
            }
        }
    }
}

/**
 * Handle the event of reading the information sent by the server
 *
 * @param key
 * @throws IOException
 */
public void read(SelectionKey key) throws IOException {
    //Same as the read method of the server
    // Server readable message: get the Socket channel where the event occurred
    SocketChannel channel = (SocketChannel) key.channel();
    // Create read buffer
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    int len = channel.read(buffer);
    if (len != -1) {
        System.out.println("Client received message:" + new String(buffer.array(), 0, len));
    }
}

}
We have seen a lot of code. Let me explain what it means. netty is based on NIO super important. We must understand. First, we created a ServerSocketChannel and a selector selector, which are set to be non blocking (fixed writing, no reason). We bound our selector to our ServerSocketChannel El, and then run selector.select(); enter the blocking state. Don't worry. This blocking has no effect. It provides us with the request to receive the client. You have no request. I'm blocking, and I won't delay you.

Back to our client, it's almost the same. When we get our NioClient and start to connect to our server, our server receives our client's request, the blocked selector.select() continues to run, and gives a SelectionKey (Iterator it = selector.selectedKeys().iterator()), which is just a small example The key mentioned in the figure, key = key, it's still reliable ~! Start to run our handle method. There is an if else. This is to say, do you want to set up a channel for the first time, write data or read data? Remember, reading and writing are relative. You can turn around by pondering several times. That's what I drew above. Take our build channel as an example. You can get ServerSocketChannel through our key key. Then you can set the next possible read-write event. Then you can see our read event. We can see that int len = sc.read(buffer) is blocked in our BIO, and our NIO is not blocked, which shows our BIO Synchronous blocking and NIO synchronous non blocking, the difference between blocking and non blocking is over. Draw a picture. Let's take a look at our NIO model.

NIO has three core components: channel, buffer and selector

Here we don't talk about Buffer. As netty will say, both channel and Buffer are bidirectional. Now let's think about my small example, the aunt of the selector guard and the key of SelectionKey. Have you got some understanding of NIO? NIO looks great, but have you ever thought about the pain of writing the above code?

AIO

AIO(NIO 2.0) is asynchronous and non blocking. After the completion of the operating system, the callback informs 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. In fact, AIO is the two times encapsulation of NIO, or how to call it NIO2.0. Let's take a brief look at the code.

Server:

package com.xiaocai.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 Exception {
final AsynchronousServerSocketChannel serverChannel =
AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));

    serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
        @Override
        public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
            try {
                // If you do not write this line of code, the client can not connect to the server
                serverChannel.accept(attachment, this);
                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 buffer) {
                        buffer.flip();
                        System.out.println(new String(buffer.array(), 0, result));
                        socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer buffer) {
                        exc.printStackTrace();
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void failed(Throwable exc, Object attachment) {
            exc.printStackTrace();
        }
    });

    Thread.sleep(Integer.MAX_VALUE);
}

}
Client:

package com.xiaocai.aio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;

public class AIOClient {

public static void main(String... args) throws Exception {
    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("Client received message:" + new String(buffer.array(), 0, len));
    }
}

}
I understand blocking and non blocking. Let's explain synchronization here. We can see that our AIO directly opens the thread in serverchannel.accept (null, new completionhandler < asynchronous socketchannel, Object > () {}. That is to say, after accept, I don't need to consider blocking any more. I can continue to run the following code, that is, asynchronous execution , the interior is still our NIO. Don't think how 6B the AIO is. The interior is the package of our NIO. The performance is actually similar to that of NIO. Sometimes it may not be as good as NIO (not measured).

Missing a knowledge point, how NIO's multiplexer works. Before our JDK 1.5, the multiplexer was traversed in the way of array and linked list, and our JDK 1.5 used hash to call back.

Conclusion:

This time we mainly talked about three IO modes of network programming: BIO, NIO and AIO. The most important is our NIO. Let's summarize the differences of the three IO modes with a picture.
1 Longhua Avenuehttp://www.kinghill.cn/LongHuaDaDao1Hao/index.html

Published 0 original articles, won praise 7, 10000 visitors+
Private letter follow

Posted by itsjames on Thu, 13 Feb 2020 04:37:40 -0800