Microservices - Remote Communication (2) BIO, NIO and AIO

BIO

BIO is blocking IO. The interactive mode of blocking IO is synchronous blocking. When a Java thread reads the input stream or writes the output stream, the thread will be blocked until the read and write actions are completed.

image.png

BIO is very resource intensive:

  • Server: The server has a receiver that is always in a blocked state waiting for a new client connection request. Whenever a new client requests a connection, it needs to create a new thread or obtain a thread from the thread pool to process the request. When the number of requests increases and the number of threads is too high, frequent switching of threads will cause serious problems, which will lead to high system load.
  • Client: The server needs a thread to block and wait for the connection request from the client. If the server does not return the request result in time, it will wait forever.

NIO

Java's non-blocking IO is implemented through three components: Channel, Buffer, and Selector. Through these components, synchronous non-blocking multiplexed I/O applications can be realized.image.png

The three major components of NIO:

  • Buffer (buffer): Buffer is essentially a memory block that can read and write data. It can be understood as a data container object (including an array). Buffer provides a set of methods to easily use memory blocks. There are multiple Buffer implementation classes in Java, the core of which is ByteBuffer.

  • Channel (channel): Channel is a two-way data read and write channel, which can read and write Buffer, and is the source of data and the destination of data writing. The Channel read operation channel.read(buffer)is to fill the data in the Channel into the Buffer, and the write operation channel.write(buffer)is to write the data in the Buffer to the Channel. There are four kinds of Channels in Java, namely FileChannel (file channel, used to read and write files), DatagramChannel (used to receive and send UDP connections), SocketChannel (TCP client channel), ServerSocketChannel (TCP server channel, It is used to listen for incoming requests on a certain port).

  • Selector(选择器):Channel可以注册到Selector上,Selector通过不断轮询注册的Channel来选择并分发已处理就绪的事件,一共有四种事件,SelectionKey.OP_READ(读事件,通道中可以读取数据)、SelectionKey.OP_WRITE(写事件,可以向通道中写入数据)、SelectionKey.OP_CONNECT(连接事件,成功建立连接)、SelectionKey.OP_ACCEPT(接收连接事件,接受到连接请求)

整个NIO的过程为:当一个客户端连接到来时,服务端会为这个新的连接创建一个Channel,并将这个Channel注册到Selector上,Selector会监听所有注册到自己身上的Channel,一旦某个Channel的数据状态发生变化,比如数据读取完毕,则会触发相关的事件,Channel的数据读取和写入都只与Buffer进行交互。

NIO服务端实现:

public class ServerExample {
    private Selector selector;

    private static final int SOCKET_PORT = 5555;

    private final int port;

    public ServerExample(){
        this(SOCKET_PORT);
    }

    public ServerExample(int port){
        this.port = port;
    }

    public void start() throws Exception{
        // TCP服务端通道,用于监听某个端口进来的请求
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 设置Socket为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 获取与该Channel关联的Socket,并绑定监听端口
        serverSocketChannel.socket().bind(new InetSocketAddress(port));

        // 获取一个Selector
        selector = Selector.open();
        // 注册Channel到Selector,并对建立连接OP_ACCEPT事件监听
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true){
            // 阻塞等待,获取就绪的事件集合
            int readyChannels = selector.select();
            if (readyChannels == 0){
                continue;
            }

            // 获取就绪的事件集合
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                handleEvent(selectionKey);
            }
        }
    }

    private void handleEvent(SelectionKey selectionKey) throws Exception{
        SocketChannel socketChannel;

        // 连接时间处理
        if (selectionKey.isAcceptable()){
            ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();
            socketChannel = server.accept();
            if (socketChannel == null){
                return;
            }

            // 设置该socketChannel为非阻塞模式
            socketChannel.configureBlocking(false);
            // 有新的连接并不代表这个通道就有数据,将Channel注册到Selector上,并监听读事件
            socketChannel.register(selector, SelectionKey.OP_READ);
        }else if (selectionKey.isReadable()){
            socketChannel = (SocketChannel) selectionKey.channel();
            // 初始化一个大小为1024的ByteBuffer
            ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
            // 将channel中的数据写入到buffer中
            int count = socketChannel.read(receiveBuffer);
            if (count > 0){
                // 打印buffer中的数据
                String content = new String(receiveBuffer.array()).trim();
                System.out.println("收到数据:" + content);
                // 初始化一个byteBuffer
                ByteBuffer sendBuffer = ByteBuffer.wrap(("Server已收到:" + content).getBytes());
                // 返回给客户端数据
                socketChannel.write(sendBuffer);
            }else if (count == -1){
                // -1 代表连接已经关闭
                System.out.println("客户端已断开链接");
                socketChannel.close();
            }

        }
    }

    public static void main(String[] args) throws Exception{
        ServerExample serverExample  = new ServerExample();
        serverExample.start();
    }
}

NIO客户端实现

public class ClientExample {
    private final String serverHost;

    private final int serverPort;

    private Selector selector;

    private SelectionKey selectionKey;

    private SocketChannel client;

    private final ExecutorService executorService = Executors.newSingleThreadExecutor();

    private volatile boolean isOver = false;

    public ClientExample(String serverHost, int serverPort) {
        this.serverHost = serverHost;
        this.serverPort = serverPort;
    }

    public void connect() {
        try {
            // 获取一个客户端Socket通道
            SocketChannel socketChannel = SocketChannel.open();
            // 设置非阻塞模式
            socketChannel.configureBlocking(false);
            // 获取一个Selector
            selector = Selector.open();
            // 注册客户端到Selector,并监听连接完成事件
            selectionKey = socketChannel.register(selector, SelectionKey.OP_CONNECT);
            // 发起连接
            socketChannel.connect(new InetSocketAddress(serverHost, serverPort));
            // 异步执行事件
            executorService.execute(this::handleEvent);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void handleEvent() {
        try {
            while (true) {
                if (isOver){
                    break;
                }
                // 阻塞等待,获取就绪的事件集合
                int readyChannels = selector.select();
                if (readyChannels == 0) {
                    continue;
                }

                // 获取就绪的事件集合
                Set<SelectionKey> readyKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = readyKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isConnectable()) {
                        client = (SocketChannel) selectionKey.channel();
                    } else if (selectionKey.isReadable()) {
                        client = (SocketChannel) selectionKey.channel();
                        // 初始化一个大小为1024的ByteBuffer
                        ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
                        // 将channel中的数据写入到buffer中
                        int count = client.read(receiveBuffer);
                        if (count > 0) {
                            // 打印buffer中的数据
                            String content = new String(receiveBuffer.array()).trim();
                            System.out.println("收到数据:" + content);
                        }
                        isOver = true;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void request() throws Exception {
        // 等待客户端Socket
        while (client == null || !client.finishConnect()){
            Thread.sleep(1000);
        }

        // 初始化一个byteBuffer
        ByteBuffer sendBuffer = ByteBuffer.wrap(("hello server!").getBytes());
        // 返回给客户端数据
        client.write(sendBuffer);
        // 设置读监听感兴趣
        selectionKey.interestOps(SelectionKey.OP_READ);
    }

    public void close()throws Exception{
        while (!isOver){
            Thread.sleep(1000);
        }
        client.close();
        executorService.shutdownNow();
        System.out.println("exit");
    }

    public static void main(String[] args) throws Exception{
        ClientExample clientExample = new ClientExample("127.0.0.1", 5555);
        clientExample.connect();
        clientExample.request();
        clientExample.close();
    }
}

AIO

AIO提供了异步非阻塞I/O操作方式,异步I/O是基于事件和回调机制实现的,也就是应用程序发起请求之后会直接返回,不会阻塞,当后台处理完成时,操作系统会通知相应的线程执行后续操作。

AIO提供了两种使用方式:

  • 将来式:Java提供Future类实现将来式,和JDK的FutureTask使用方式一样,将执行任务交给线程池执行后,执行任务的线程不会阻塞,而是获得一个Future对象,可以使用get()方法阻塞等待任务完成并获取结果。
  • 回调式:Java提供了CompletionHandler作为回调接口,在调用read()、write()等方法时,可以传入CompletionHandler的实现作为事件完成的回调接口。

AsynchronousServerSocketChannel对应的是非阻塞IO的ServerSocketChannel,AIO服务端实现为:

public class ServerExample {
    private static final int SOCKET_PORT = 5555;

    private final int port;

    public ServerExample() {
        this(SOCKET_PORT);
    }

    public ServerExample(int port) {
        this.port = port;
    }

    public void start() throws Exception {
        // 实例化,并监听端口
        AsynchronousServerSocketChannel server =
                AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(SOCKET_PORT));

        // accept、read和write方法都有一个attachment参数,该参数可以作为上下文传递一些自定义参数
        server.accept(new Object(), new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Object object) {
                try {
                    SocketAddress clientAddr = client.getRemoteAddress();
                    System.out.println("收到新的连接:" + clientAddr);

                    // 收到新的连接后,server重新调用accept方法等待新的连接进来
                    server.accept(object, this);

                    // read和write方法都可以设置CompletionHandler
                    ByteBuffer buffer = ByteBuffer.allocate(2048);
                    client.read(buffer, true, new CompletionHandler<Integer, Boolean>() {
                        public void completed(Integer result, Boolean isReadMode) {
                            if (isReadMode) {
                                // 读取来自客户端的数据
                                buffer.flip();
                                byte bytes[] = new byte[buffer.limit()];
                                buffer.get(bytes);
                                String msg = new String(buffer.array()).trim();
                                System.out.println("收到来自客户端的数据: " + msg);

                                // 响应客户端请求,返回数据
                                buffer.clear();
                                buffer.put("Response from server!".getBytes(Charset.forName("UTF-8")));
                                buffer.flip();
                                // 写数据到客户端
                                client.write(buffer, false, this);
                            } else {
                                // 到这里,说明往客户端写数据也结束了,有以下两种选择:
                                // 1. 继续等待客户端发送新的数据过来
                                // buffer.clear();
                                // client.read(buffer, true, this);
                                // 2. 断开这次的连接
                                try {
                                    client.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }

                        public void failed(Throwable t, Boolean att) {
                            System.out.println("连接断开");
                        }
                    });
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable t, Object object) {
                System.out.println("accept failed");
            }
        });
        // 防止main线程退出
        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        ServerExample serverExample = new ServerExample();
        serverExample.start();
    }
}

AsynchronousSocketChannel对应的是非阻塞IO的SocketChannel,AIO客户端实现为:

public class ClientExample {
    private final String serverHost;

    private final int serverPort;

    public ClientExample(String serverHost, int serverPort) {
        this.serverHost = serverHost;
        this.serverPort = serverPort;
    }

    public void connect() throws Exception {
        AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
        // Future形式
        Future<?> future = client.connect(new InetSocketAddress(serverHost, serverPort));
        // 阻塞等待连接成功
        future.get();

        ByteBuffer buffer = ByteBuffer.allocate(2048);
        byte[] data = "I am client!".getBytes();
        buffer.put(data);
        buffer.flip();

        // 异步发送数据到服务端
        client.write(buffer, false, new CompletionHandler<Integer, Boolean>() {
            public void completed(Integer result, Boolean isReadMode) {
                if (isReadMode) {
                    // 读取来自服务端的数据
                    buffer.flip();
                    byte[] bytes = new byte[buffer.limit()];
                    buffer.get(bytes);
                    String msg = new String(bytes, Charset.forName("UTF-8"));
                    System.out.println("收到来自服务端的响应数据: " + msg);

                    // 有以下两种选择:
                    // 1. 向服务端发送新的数据
                    // buffer.clear();
                    // String newMsg = "new message from client";
                    // byte[] data = newMsg.getBytes(Charset.forName("UTF-8"));
                    // buffer.put(data);
                    // buffer.flip();
                    // client.write(buffer, false, this);
                    // 2. 关闭连接
                    try {
                        client.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 写操作完成后,读取数据
                    buffer.clear();
                    client.read(buffer, true, this);
                }
            }

            public void failed(Throwable t, Boolean isReadMode) {
                System.out.println("服务器无响应");
            }
        });

        // 这里休息一下再退出,给出足够的时间处理数据
        Thread.sleep(2000);
    }


    public static void main(String[] args) throws Exception {
        ClientExample clientExample = new ClientExample("127.0.0.1", 5555);
        clientExample.connect();
    }
}

Guess you like

Origin juejin.im/post/7235433635699736636