Java Network IO Programming (BIO, NIO, AIO)

Keywords: Java socket Programming network

This concept

BIO programming

Traditional BIO programming

Code example:

public class Server {
    final static int PROT = 8765;

    public static void main(String[] args) {
        ServerSocket server = null;
        try {
            server = new ServerSocket(PROT);
            System.out.println(" server start .. ");
            // Blocking
            Socket socket = server.accept();
            // Create a new thread to perform client tasks
            new Thread(new ServerHandler(socket)).start();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (server != null) {
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            server = null;
        }
    }
}


public class ServerHandler implements Runnable {

    private Socket socket;

    public ServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String body = null;
            while (true) {
                body = in.readLine();
                if (body == null)
                    break;
                System.out.println("Server :" + body);
                out.println("Server-side echo data.");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            socket = null;
        }
    }
}


public class Client {
    final static String ADDRESS = "127.0.0.1";
    final static int PORT = 8765;
    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;

        try {
            socket = new Socket(ADDRESS, PORT);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);

            // Send data to the server
            out.println("Receive client request data...");
            // out.println("Receive client request data 1111...");
            String response = in.readLine();
            System.out.println("Client: " + response);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            socket = null;
        }
    }
}

The biggest problem of this model is the lack of flexibility. When the concurrent visits of clients increase, the number of threads on the server side is proportional to the number of concurrent visits on the client side by 1:1. Threads in Java are also valuable system resources. After the rapid expansion of the number of threads, the performance of the system will decline dramatically. With the continuous increase of the visits, the system will eventually die-out.

Pseudo-Asynchronous I/O Programming

Code example:

public class Server {
    final static int PORT = 8765;
    public static void main(String[] args) {
        ServerSocket server = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            server = new ServerSocket(PORT);
            System.out.println("server start");
            Socket socket = null;
            HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
            while (true) {
                socket = server.accept();
                executorPool.execute(new ServerHandler(socket));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if (server != null) {
                try {
                    server.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            server = null;
        }
    }
}


public class ServerHandler implements Runnable {

    private Socket socket;

    public ServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String body = null;
            while (true) {
                body = in.readLine();
                if (body == null)
                    break;
                System.out.println("Server:" + body);
                out.println("Server response");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            socket = null;
        }
    }
}


public class HandlerExecutorPool {

    private ExecutorService executor;
    public HandlerExecutorPool(int maxPoolSize, int queueSize){
        this.executor = new ThreadPoolExecutor(
                Runtime.getRuntime().availableProcessors(),
                maxPoolSize, 
                120L, 
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(queueSize));
    }
    
    public void execute(Runnable task){
        this.executor.execute(task);
    }
}


public class Client {

    final static String ADDRESS = "127.0.0.1";
    final static int PORT = 8765;

    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            socket = new Socket(ADDRESS, PORT);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);

            out.println("Client request");

            String response = in.readLine();
            System.out.println("Client:" + response);

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            socket = null;
        }
    }
}

By using thread pool, we can effectively control the maximum number of threads, ensure the control of the limited resources of the system, and realize the pseudo-asynchronous I/O model of N:M. However, because of the limited number of threads, if a large number of concurrent requests occur, more than the maximum number of threads can only wait until the idle threads in the thread pool can be reused. When the input stream of Socket is read on line, it will be blocked until it occurs:

  • Data readable
  • Available data and finished reading
  • Null pointer or I/O exception occurs

Therefore, when reading data slowly (such as large amount of data, slow network transmission, etc.) and large amount of concurrency, other access messages can only wait all the time, which is the biggest drawback.

NIO programming

brief introduction

 

NIO provides two different socket channel implementations, Socket Channel and Server Socket Channel, corresponding to the Socket and Server Socket in the traditional BIO model.

Both new channels support both blocking and non-blocking modes.

Blocking mode is as simple as traditional support, but its performance and reliability are not good; non-blocking mode is just the opposite.

For low-load, low-concurrent applications, synchronous blocking I/O can be used to improve development speed and better maintainability; for high-load, high-concurrent (network) applications, NIO non-blocking mode should be used to develop.

Buffer

Buffer is an object that contains some data to be written or read out.

In NIO libraries, all data is processed with buffers. When reading data, it reads directly to the buffer; when writing data, it also writes to the buffer. Accessing data in NIO at any time is done through buffers.

Buffers are actually arrays that provide structured access to data and maintenance of read and write locations.

Specific caches are ByteBuffe r, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, Float Buffer, DoubleBuffer. They implemented the same interface: Buffer.

Reference to http://ifeve.com/buffers/

Channel

We read and write data through Channel, which is like a water pipe, a channel. Channels differ from streams in that they are bidirectional and can be used for reading, writing and simultaneous reading and writing operations.

The channel of the underlying operating system is generally full-duplex, so the full-duplex Channel can better map the API of the underlying operating system than the stream.

Channel falls into two main categories:

  • Selectable Channel: User Network Read and Write
  • FileChannel: Used for file manipulation

ServerSocketChannel and SocketChannel, which will be covered in the later code, are subclasses of Selectable Channel.

Multiplexer Selector

Selector is the basis of Java NIO programming.

Selector provides the ability to select ready tasks: Selector polls the Channel registered on it continuously, and if a read or write event occurs on a Channel, the Channel is ready, polled by the Selector, and then a collection of ready Channels can be obtained through SelectionKey for subsequent I/O operations.

A Selector can poll multiple Channel s at the same time, because JDK uses epoll() instead of traditional select implementation, so there is no limitation of maximum connection handle 1024/2048. So, just one thread is responsible for polling the Selector, and thousands of clients can be accessed.

 

Code example:

public class Server implements Runnable {
    // 1 Multiplexer (managing all channels)
    private Selector seletor;
    // 2 Buffer Establishment
    private ByteBuffer readBuf = ByteBuffer.allocate(1024);
    // 3
    private ByteBuffer writeBuf = ByteBuffer.allocate(1024);

    public Server(int port) {
        try {
            // 1 Open circuit multiplexer
            this.seletor = Selector.open();
            // 2 Open Server Channel
            ServerSocketChannel ssc = ServerSocketChannel.open();
            // 3 Setting Server Channel in Non-blocking Mode
            ssc.configureBlocking(false);
            // 4 Binding address
            ssc.bind(new InetSocketAddress(port));
            // 5 Register server channels on multiplexers and listen for blocking events
            ssc.register(this.seletor, SelectionKey.OP_ACCEPT);

            System.out.println("Server start, port :" + port);

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

    @Override
    public void run() {
        while (true) {
            try {
                // 1 Multiplexers must be allowed to start listening
                // Blocking, Waiting for Client Operations (Connecting or Writing Data)
                // When the client is connected, key by isAcceptable;When the client enters data, key by isReadable;
                this.seletor.select();
                // 2 Returns the result set selected by the multiplexer
                Iterator<SelectionKey> keys = this.seletor.selectedKeys().iterator();
                // 3 Traversal
                while (keys.hasNext()) {
                    // 4 Get a selected element
                    SelectionKey key = keys.next();
                    // 5 Just remove it from the container.
                    keys.remove();
                    // 6 If it is valid
                    if (key.isValid()) {
                        // 7 If it is blocked
                        if (key.isAcceptable()) {
                            this.accept(key);
                        }
                        // 8 If it is readable
                        if (key.isReadable()) {
                            this.read(key);
                        }
                        // 9 Writing data
                        if (key.isWritable()) {
                            // this.write(key); //ssc
                        }
                    }

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

    // Writing data to the client is done by ServerSocketChannel To write
    private void write(SelectionKey key) {
        // ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        // ssc.register(this.seletor, SelectionKey.OP_WRITE);
    }

    private void read(SelectionKey key) {
        try {
            // 1 Clean up old data in the buffer
            this.readBuf.clear();
            // 2 Pre-registered socket Channel object
            SocketChannel sc = (SocketChannel) key.channel();
            // 3 Read data
            int count = sc.read(this.readBuf);
            // 4 If there is no data
            if (count == -1) {
                key.channel().close();
                key.cancel();
                return;
            }
            // 5 If there is data, reset method is needed before reading and reading.(hold position and limit Reset)
            this.readBuf.flip();
            // 6 Create the corresponding size based on the data length of the buffer byte Array, receiving buffer data
            byte[] bytes = new byte[this.readBuf.remaining()];
            // 7 Receiving Buffer Data
            this.readBuf.get(bytes);
            // 8 Print results
            String body = new String(bytes).trim();
            System.out.println("Server : " + body);

            // 9..Can write back to the client data

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

    }

    private void accept(SelectionKey key) {
        try {
            // 1 Access to Service Channel
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            // 2 Execution blocking method
            SocketChannel sc = ssc.accept();
            // 3 Setting Blocking Mode
            sc.configureBlocking(false);
            // 4 Register on multiplexer and set read identification
            sc.register(this.seletor, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        new Thread(new Server(8765)).start();
        ;
    }

}


public class Client {

    // Need one Selector
    public static void main(String[] args) {

        // Address to create a connection
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765);

        // Declare connection channels
        SocketChannel sc = null;

        // Buffer Establishment
        ByteBuffer buf = ByteBuffer.allocate(1024);

        try {
            // Open channel
            sc = SocketChannel.open();
            // Link up
            sc.connect(address);

            while (true) {
                // Define a byte array and then use the system entry function:
                byte[] bytes = new byte[1024];
                System.in.read(bytes);

                // Put the data in the buffer
                buf.put(bytes);
                // Reset the buffer
                buf.flip();
                // write
                sc.write(buf);
                // Clear buffer data
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sc != null) {
                try {
                    sc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

AIO programming

Code example:

public class Server {
    // Thread pool
    private ExecutorService executorService;
    // Thread group
    private AsynchronousChannelGroup threadGroup;
    // Server channel
    public AsynchronousServerSocketChannel assc;

    public Server(int port) {
        try {
            // Create a cache pool
            executorService = Executors.newCachedThreadPool();
            // Create Thread Groups
            threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
            // Create a server channel
            assc = AsynchronousServerSocketChannel.open(threadGroup);
            // Binding
            assc.bind(new InetSocketAddress(port));

            System.out.println("server start , port : " + port);
            // Blocking
            assc.accept(this, new ServerCompletionHandler());
            // Keep the server blocked until it stops
            Thread.sleep(Integer.MAX_VALUE);

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

    public static void main(String[] args) {
        Server server = new Server(8765);
    }
}


public class ServerCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, Server> {

    @Override
    public void completed(AsynchronousSocketChannel asc, Server attachment) {
        // Call directly when the next client accesses Server Of accept Method, so that repeated execution, to ensure that multiple clients can block
        attachment.assc.accept(attachment, this);
        read(asc);
    }

    private void read(final AsynchronousSocketChannel asc) {
        // Read data
        ByteBuffer buf = ByteBuffer.allocate(1024);
        asc.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer resultSize, ByteBuffer attachment) {
                // After reading,Reset identification bit
                attachment.flip();
                // Get the number of bytes read
                System.out.println("Server -> " + "The length of data received from the client is:" + resultSize);
                // Get the read data
                String resultData = new String(attachment.array()).trim();
                System.out.println("Server -> " + "The data information received from the client is:" + resultData);
                String response = "Server response, Received data from client: " + resultData;
                write(asc, response);
            }

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

    private void write(AsynchronousSocketChannel asc, String response) {
        try {
            ByteBuffer buf = ByteBuffer.allocate(1024);
            buf.put(response.getBytes());
            buf.flip();
            asc.write(buf).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

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


public class Client implements Runnable{

    private AsynchronousSocketChannel asc ;
    
    public Client() throws Exception {
        asc = AsynchronousSocketChannel.open();
    }
    
    public void connect(){
        asc.connect(new InetSocketAddress("127.0.0.1", 8765));
    }
    
    public void write(String request){
        try {
            asc.write(ByteBuffer.wrap(request.getBytes())).get();
            read();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void read() {
        ByteBuffer buf = ByteBuffer.allocate(1024);
        try {
            asc.read(buf).get();
            buf.flip();
            byte[] respByte = new byte[buf.remaining()];
            buf.get(respByte);
            System.out.println(new String(respByte,"utf-8").trim());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void run() {
        while(true){
            
        }
    }
    
    public static void main(String[] args) throws Exception {
        Client c1 = new Client();
        c1.connect();
        
        Client c2 = new Client();
        c2.connect();
        
        Client c3 = new Client();
        c3.connect();
        
        new Thread(c1, "c1").start();
        new Thread(c2, "c2").start();
        new Thread(c3, "c3").start();
        
        Thread.sleep(1000);
        
        c1.write("c1 aaa");
        c2.write("c2 bbbb");
        c3.write("c3 ccccc");
    }
}

Comparison of various I/O

First, a table is used to make an intuitive comparison.

    

Specific choice of model or NIO framework is based entirely on the practical application scenarios and performance requirements of the business. If the client is small and the server load is not heavy, it is not necessary to choose NIO which is relatively less simple to develop as the server; on the contrary, we should consider using NIO or related framework (Netty,Nima).

Posted by ziola on Sat, 15 Dec 2018 21:09:05 -0800