This article is in Java Server, which contains my entire Series of Java articles for study or interview

(1) Introduction

IO streams are difficult to understand in Java, but IO streams are often used in actual development scenarios, such as Dubbo underlying NIO for communication. This article introduces the three IO types that have emerged during the development of Java: BIO, NIO, and AIO, with an emphasis on NIO.

(2) What is BIO

BIO blocks IO synchronously, implementing the model that a connection requires a thread to process it. In simple terms, when a client requests the server, the server starts a thread to process the request, even if the request does nothing, the thread remains blocked.

BIO model has many disadvantages, the biggest disadvantage is the waste of resources. Imagine if QQ uses the BIO model. When a person is online, a thread is required. Even if the person is not chatting, the thread is occupied all the time.

(iii) BIO code practice

We simulate the BIO implementation logic through sockets

Set up the Server, set up a ServerSocket object, bind the port, and wait for the connection. If the connection is successful, create a new thread to process the connection.

public class server {
    private static Socket socket=null;
    public static void main(String[] args) {
        try {
            // Bind ports
            ServerSocket serverSocket=new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080));
            while (true) {// Wait for the connection to block
                System.out.println("Waiting for connection");
                socket = serverSocket.accept();
                System.out.println("Connection successful");
                // open a new thread to process the connection
                new Thread(new Runnable() {
                    @Override
                    public void run(a) {
                        byte[] bytes=new byte[1024];
                        try {
                            System.out.println("Waiting to read data");
                            // Waiting to read data is blocked
                            int length=socket.getInputStream().read(bytes);
                            System.out.println(new String(bytes,0,length));
                            System.out.println("Data read successful");
                        } catch(IOException e) { e.printStackTrace(); } } }).start(); }}catch(IOException e) { e.printStackTrace(); }}}Copy the code

The Client code is then set up

public class Client {
    public static void main(String[] args) {
        Socket socket= null;
        try {
            socket = new Socket("127.0.0.1".8080);
            socket.getOutputStream().write("A piece of data".getBytes());
            socket.close();
        } catch(IOException e) { e.printStackTrace(); }}}Copy the code

The client code simply connects to a server and sends a piece of data.

This implemented a BIO, but the drawbacks of BIO were so obvious that NIO was introduced in JDK1.4.

(4) What is NIO

The BIO is blocking, and without multithreading, the BIO would have to occupy the CPU all the time, while NIO is non-blocking IO, and NIO does not block a program when it receives a connection or request, even if it does not receive a connection or data. NIO’s server implementation pattern is that one thread can handle multiple requests (connections).

There are a few things to know about NIO: channels, buffers, and selectors.

A Channel can be used for both read and write operations. Common NIO channels include FileChannel, SocketChannel, ServerSocketChannel, and DatagramChannel.

Buffer Buffers are used to send and receive data.

A Selector is usually called a Selector or a multiplexer. It is one of the Java NIO core components that checks whether the state of one or more NIO channels is readable and writable. Using a Selector in javaNIO usually registers a Channel with a Selector.

Let me simulate the javaNIO process in code.

(v) NIO code practice

First post NIO practice code:

The NIO server details the execution process as follows:

1. Create a ServerSocketChannel and Selector, and register the ServerSocketChannel with the Selector

2. Selector polls for channel events using the select method, and listens for connection events if a client is trying to connect.

3. Bind a SocketChannel to a ServerSocketChannel using the channel method. Bind a SocketChannel to a ServerSocketChannel using the SelectorKey.

Socketchannel registers with Selector and cares about read events.

5. Selector uses the Select method to poll for a channel event. When a read event is heard, the ServerSocketChannel uses the bound SelectorKey to locate the specific channel and read the data inside.

public class NioServer {
    public static void main(String[] args) throws IOException {
        // Create a socket channel and set it to non-blocking
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(9000));
        // Create a selector selector and register a channel with the selector selector
        Selector selector=Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true){
            System.out.println("Waiting for an event to happen.");
            selector.select();
            System.out.println("Something has happened.");
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){ SelectionKey key = iterator.next(); iterator.remove(); handle(key); }}}private static void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()){
            System.out.println("Connection event occurred");
            ServerSocketChannel serverSocketChannel= (ServerSocketChannel) key.channel();
            // Create a client-side channel and register it with a selector
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(key.selector(),SelectionKey.OP_READ);
        }else if (key.isReadable()){
            System.out.println("Data readable event occurred");
            SocketChannel socketChannel= (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = socketChannel.read(buffer);
            if(len! = -1){
                System.out.println("Read data sent by client:"+new String(buffer.array(),0,len));
            }
            // Send information to the client
            ByteBuffer wrap = ByteBuffer.wrap("hello world".getBytes()); socketChannel.write(wrap); key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE); socketChannel.close(); }}}Copy the code

Client code: NIO client code is much more complex to implement than BIO. The main difference is that NIO clients also need to poll themselves for connections to the server.

public class NioClient {
    public static void main(String[] args) throws IOException {
        // Set basic connection parameters
        SocketChannel channel=SocketChannel.open();
        channel.configureBlocking(false);
        Selector selector = Selector.open();
        channel.connect(new InetSocketAddress("127.0.0.1".9000));
        channel.register(selector, SelectionKey.OP_CONNECT);

        // Polls to access selector
        while(true){
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                // A connection event occurred
                if (key.isConnectable()){
                    SocketChannel socketChannel= (SocketChannel) key.channel();
                    // If you are connecting, complete the connection
                    if (socketChannel.isConnectionPending()){
                        socketChannel.finishConnect();
                    }
                    socketChannel.configureBlocking(false);
                    ByteBuffer buffer = ByteBuffer.wrap("Data sent by client".getBytes());
                    socketChannel.write(buffer);
                    socketChannel.register(selector,SelectionKey.OP_READ);
                }else if (key.isReadable()){
                    // Read the message sent by the serverread(key); }}}}private static void read(SelectionKey key) throws IOException {
        SocketChannel socketChannel= (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(512);
        int len=socketChannel.read(buffer);
        if(len! = -1){
            System.out.println("Client receives message:"+new String(buffer.array(),0,len)); }}}Copy the code

The effect is something like this: first the server waits for an event to occur. When the client starts, the server receives a connection request, then a data read request, and then waits.

After the client sends data, it receives a reply from the server.

(vi) NIO summary

NIO listens for various IO events through a Selector, which is then handled by a thread at the back end. NIO, in contrast to BIO, is non-blocking in polling processing. The BIO back-end thread blocks waiting for the client to write data and remains blocked if the client does not write data. NIO, on the other hand, polls registered clients with Selector, handing them to the back end when an event occurs, and the back end thread doesn’t have to wait.

(7) What is AIO

AIO is a new I/o method introduced in JDK1.7 — asynchronous non-blocking I/o, also known as NIO2.0. When performing read and write operations, AIO directly calls the API’s read and write methods, both of which are asynchronous and will actively call the callback function after completion. In simple terms, when a stream is available to read, the operating system passes the readable stream into the buffer of the read method and notifies the application. For write operations, the operating system actively notifies the application when the stream passed by the write method has been written.

Java provides four asynchronous channels: AsynchronousSocketChannel, AsynchronousServerSocketChannel, AsynchronousFileChannel, AsynchronousDatagramChannel.

(8) AIO code practice

Server-side code: AIO is created in a similar way to NIO, creating channels, binding, and listening. AIO uses asynchronous channels.

public class AIOServer {
    public static void main(String[] args) {
        try {
            // Create an asynchronous channel
            AsynchronousServerSocketChannel serverSocketChannel=AsynchronousServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            System.out.println("Waiting for connection");
            // In AIO, accept takes two arguments,
            // The first argument is a generic, which can be used to control the object you want to pass
            // The second argument, CompletionHandler, handles the logic for listening on success and failure
            // The reason for setting the listener like this is that it is a recursive operation, each time the listener succeeds, the next listener is opened
            serverSocketChannel.accept(null.new CompletionHandler<AsynchronousSocketChannel, Object>() {
                // Request success processing logic
                @Override
                public void completed(AsynchronousSocketChannel result, Object attachment) {
                    System.out.println("Connection successful, processing data.");
                    // Start a new listener
                    serverSocketChannel.accept(null.this);
                    handledata(result);
                }
                @Override
                public void failed(Throwable exc, Object attachment) {
                    System.out.println("Failure"); }});try {
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
            } catch(InterruptedException e) { e.printStackTrace(); }}catch(IOException e) { e.printStackTrace(); }}private static void handledata(AsynchronousSocketChannel result) {
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        The channel's read method also takes three arguments
        //1. Destination: handles the transfer cache of data transferred by the client
        //2. The object that handles data passed by the client
        //3. There are successful and unsuccessful ways of writing logic
        result.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                if (result>0){
                    attachment.flip();
                    byte[] array = attachment.array();
                    System.out.println(newString(array)); }}@Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                System.out.println("Failure"); }}); }}Copy the code

The client code is basically the same, and the main function is to send the data

public class AIOClient {
    public static void main(String[] args) {
        try {
            AsynchronousSocketChannel socketChannel=AsynchronousSocketChannel.open();
            socketChannel.connect(new InetSocketAddress("127.0.0.1".8080));
            Scanner scanner=new Scanner(System.in);
            String next = scanner.next();
            ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
            byteBuffer.put(next.getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
        } catch(IOException e) { e.printStackTrace(); }}}Copy the code

Observations: