微服务-远程通信(二)BIO、NIO和AIO

BIO

BIO即阻塞式IO,阻塞式IO的交互方式是同步阻塞方式,当一个Java线程在读入输入流或者写入输出流时,在读写动作完成之前,线程一直会被阻塞。

image.png

BIO是非常消耗资源的:

  • 服务端:服务端有一个接收器一直处于阻塞状态等待新的客户端连接请求,每当有新的客户端请求连接时,都需要创建新的线程或者从线程池中获取线程来处理请求。当请求量增大,线程数过高时,线程的频繁切换会带来严重的问题,它会导致系统负载偏高。
  • 客户端:服务端需要一个线程一直阻塞等待客户端的连接请求,若服务端没有及时返回请求结果而会一直等待。

NIO

Java的非阻塞IO是通过Channel、Buffer和Selector三个组件实现,通过这些组件,可以实现同步非阻塞的多路复用I/O应用程序。 image.png

NIO的三大组件:

  • Buffer(缓冲区):Buffer本质上是一个可读写数据的内存块,可以理解成是一个数据容器对象(含数组),Buffer提供了一组方法,可以轻松地使用内存块。Java中有多个Buffer实现类,其中最核心的ByteBuffer。

  • Channel(通道):Channel是一个双向的数据读写通道,可以对Buffer进行读写操作,是数据的来源和数据写入的目的地。Channel读操作channel.read(buffer)是将Channel中的数据填充到Buffer中,而写操作channel.write(buffer)是将Buffer中的数据写入到Channel中。Java中有四种Channel,分别为FileChannel(文件通道,用于文件的读和写)、DatagramChannel(用于 UDP 连接的接收和发送)、SocketChannel(TCP 客户端通道)、ServerSocketChannel(TCP服务端通道,用于监听某个端口进来的请求)。

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

猜你喜欢

转载自juejin.im/post/7235433635699736636