从JavaApi的角度一次搞懂五种IO模型~附服务端和客户端代码


在说IO模型之前,我们得先知道java程序发起read请求时,操作系统到底干了什么?

操作系统层面的IO

在linux上基于安全的考虑,用户态的CPU只能访问用户空间内核态的CPU可以访问整个进程空间。并且,只有内核态下可以直接访问各种硬件资源,比如磁盘和网卡。

当CPU处于Ring3时为用户态,处于Ring0时为内核态。

当我们的程序发起阻塞read时,因为网络数据是通过网卡来接收的。处于用户态的CPU无法直接读取网卡中的数据,所以会发生系统调用。然后CPU会从用户态升级到内核态。这时,如果数据未就绪,线程就会阻塞。当系统调用返回时,CPU从内核态重新切换为用户态,并且可以从用户空间的buffer中读到数据。

也就是说,当系统调用返回时,要读取的数据已经从网卡拷贝到内核空间再拷贝到用户空间了

Socket Read的过程

  1. 用户态CPU执行应用程序代码
  2. 应用程序发起read操作
  3. 产生系统调用,CPU由用户态切换为内核态
  4. 如果要读取的数据尚未就绪,那么当前线程阻塞(其实是将进程的task_struct从运行队列中移动到等待队列中,并触发一次CPU调用,这样就让出了CPU,同时线程处于等待状态)
  5. 当数据就绪后,数据从内核空间拷贝到用户空间。再将之前阻塞的线程的task_struct从等待队列中移除,重新放入就运行队列中。触发CPU调度后,系统调用返回
  6. 应用程序读取到数据

思考

从上面的逻辑中可以看出,一次read操作要经历两个大步骤,分别对应上面的步骤4和步骤5:

  1. 等待数据就绪,就是等数据从网卡拷贝到内核空间中
  2. 将数据从内核空间复制到用户空间,因为应用程序是运行在用户态的,数据不从内核空间拷贝到用户空间的话,应用程序就无法读到数据。

各种IO模型就是在从不同的角度实现上面的步骤。

五大IO模型

首先:

  • IO模型是为了解决内存和外部设备速度差异问题的。
  • 阻塞与非阻塞:是指应用程序发起IO操作时,是否立即返回。立即返回为非阻塞,不能立即返回为阻塞。
  • 同步与异步:说的是数据从内核空间拷贝到用户空间时,采用的是同步还是异步的方式,目前只有异步IO才是异步拷贝,其他的IO模型都是同步拷贝。

五大IO模型
接下来我们以计算服务的demo来应用一下各种IO模型

所有的Server都是用main函数启动,所有的Client都是@Test写在测试包下的

信号驱动IO在实际中并不常用,貌似java中也没有现成的api可以用,所以就先略过了

同步阻塞IO

最最传统的IO操作模式,程序员友好度非常高。缺点是每一个请求都需要创建一个新的线程来处理,对性能开销影响比较大,并不适合高并发的场景。但是如果是连接数小且相对简单的服务也可以用这种IO模型。其实没有空转,不浪费CPU也算是BIO的优点了。
图片转自
同步阻塞IO

BIOServer

public class BIOServer {
    private static final Logger logger = LoggerFactory.getLogger(BIOServer.class);

    public static void main(String[] args) throws IOException {
        ThreadPoolExecutor bizThreadPoolExecutor = createBizThreadPool();
        ServerSocket serverSocket = new ServerSocket(8080);
        while (true) {
            logger.info("等待新连接");
            //这里用while(true)来接收请求,只要业务处理线程池还吃得下,就可以一直接收请求
            //由于是使用线程池处理请求的具体内容,所以就算是连接上了,也不一定能立刻处理
            Socket accept = serverSocket.accept();
            logger.info("获取到连接了{}", accept);
            bizThreadPoolExecutor.execute(() -> {
                handleSocket(accept);
            });
        }
    }

    private static ThreadPoolExecutor createBizThreadPool() {
        return new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, new SynchronousQueue<>(),
                new ThreadFactory() {
                    private final AtomicInteger atomicInteger = new AtomicInteger();

                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "BIOServer-" + atomicInteger.getAndIncrement());
                    }
                },
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        logger.info("当前没有可用的线程了,任务被挂起");
                        try {
                            executor.getQueue().put(r);
                            logger.info("任务成功入队");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
    }

    private static void handleSocket(Socket socket) {
        logger.info("业务线程处理连接,{}", socket);
        InputStream inputStream = null;
        OutputStream out = null;
        try {
            StringBuilder builder = new StringBuilder();
            inputStream = socket.getInputStream();
            out = socket.getOutputStream();
            byte[] bytes = new byte[1024];
            //同步阻塞读取,如果没有可读的,就会在这里阻塞
            //如果是需要有返回值的请求,那这里就不能while(read!=-1),必须一次性读取
            //如果while(read!=-1),当客户端的socket不关闭时,服务端会一直阻塞在inputStream.read(bytes)
            //所以bytes的大小比较关键
            int read = inputStream.read(bytes);
            if(read==-1){
                return;
            }
            builder.append(new String(bytes, 0, read));
            logger.info("读取到来自客户端的数据[{}]", builder);
            Object result;
            try {
                result = AviatorEvaluator.execute(builder.toString());
                logger.info("AviatorEvaluator的计算结果是[{}]", result);
            } catch (Exception e) {
                result = e.getMessage();
            }
            out.write(result.toString().getBytes(StandardCharsets.UTF_8));
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

如果使用BIOServer,那一定要附带一个线程池来处理请求,否则第一个请求没处理完,第二个请求就接不进来。

BIOClient

public class BIOClient {

    private static final Logger logger = LoggerFactory.getLogger(BIOClient.class);

    @Test
    public void test1() throws IOException {
        testBIOClient("22+33+44+55");
    }

    private void testBIOClient(String exp) throws IOException {
        logger.info("开始建立连接");
        long timeMillis = System.currentTimeMillis();
        Socket socket =new Socket("localhost",8080);
        logger.info("连接建立成功,耗时{}毫秒",System.currentTimeMillis()-timeMillis);
        OutputStream outputStream = socket.getOutputStream();
        logger.info("传入计算表达式:{}",exp);
        outputStream.write(exp.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        InputStream inputStream = socket.getInputStream();
        int read;
        byte[] bytes = new byte[1024];
        StringBuilder builder = new StringBuilder();
        while((read=inputStream.read(bytes))!=-1){
            builder.append(new String(bytes,0,read));
        }
        logger.info("服务器返回结果为:{}", builder);
        inputStream.close();
        outputStream.close();
        socket.close();
    }
}

在各个版本的client中,BIOClient算是最最舒服的了,代码从上写到下,一气呵成。

运行结果

  • Server端
    在这里插入图片描述
  • Client端
    在这里插入图片描述

同步非阻塞IO

由于是非阻塞的,所以程序中很多地方都需要用到while(true)。优点:非阻塞,当客户端建立连接之后,如果还没有发送数据,这时服务端不会卡住。缺点:非阻塞,到处都是while(true),性能上得不偿失。
图片转自
在这里插入图片描述

NIOServer

public class NIOServer {
    private static final Logger logger = LoggerFactory.getLogger(NIOServer.class);

    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8080));
        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        ArrayBlockingQueue<SocketChannel> channels = new ArrayBlockingQueue<>(50);
        Thread socketWatcher = new Thread(() -> {
            logger.info("socket处理线程启动");
            while (true) {
                SocketChannel socket = null;
                try {
                    socket = channels.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (socket == null) {
                    continue;
                }
                boolean finish = handleSocket(socket);
                if (!finish) {
                    try {
                        channels.put(socket);
                        logger.info("{}还没有可读的数据,又放回待处理队列了", socket);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    logger.info("{}已处理完毕", socket);
                }
            }
        }, "socketWatcher");
        socketWatcher.start();
        while (true) {
            logger.info("等待新连接");
            SocketChannel accept = serverSocketChannel.accept();
            if (accept == null) {
                //如果当前没有新连接,那就等1秒再看看
                logger.info("没有新的连接,等1秒再看看");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                logger.info("获得新连接,{}", accept);
                try {
                    channels.put(accept);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static boolean handleSocket(SocketChannel socketChannel) {
        logger.info("业务线程处理连接,{}", socketChannel);
        boolean finish = false;
        try {
            //手动设置为非阻塞模式
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
        StringBuilder builder = new StringBuilder();
        try {
            //因为设置了非阻塞模式,所以read这里并不会阻塞,但是如果没有数据会返回0
            int read=socketChannel.read(byteBuffer);
            //在没有数据时,不进行下一步处理
            if (read==0 || read==-1) {
                return false;
            }
            byteBuffer.flip();
            builder.append(StandardCharsets.UTF_8.decode(byteBuffer));
            byteBuffer.clear();
            logger.info("读取到来自客户端的数据[{}]", builder);
            Object result;
            try {
                result = AviatorEvaluator.execute(builder.toString());
                logger.info("AviatorEvaluator的计算结果是[{}]", result);
            } catch (Exception e) {
                result = e.getMessage();
            }
            byteBuffer.put(result.toString().getBytes(StandardCharsets.UTF_8));
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            finish = true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (finish) {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return finish;
    }
}

NIOClient

public class NIOClient {
    private static final Logger logger = LoggerFactory.getLogger(NIOClient.class);

    @Test
    public void test1() throws IOException {
        test("22+33+44+55");
    }

    private void test(String exp) throws IOException {
        logger.info("开始建立连接");
        long timeMillis = System.currentTimeMillis();
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress(8080));
        boolean finishConnect;
        do {
            finishConnect = socketChannel.finishConnect();
        }while (!finishConnect);
        logger.info("连接建立成功,耗时{}毫秒",System.currentTimeMillis()-timeMillis);
        logger.info("传入计算表达式:{}",exp);
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
        byteBuffer.put(exp.getBytes(StandardCharsets.UTF_8));
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
        byteBuffer.clear();
        int read;
        do {
            read = socketChannel.read(byteBuffer);
        }while (read==0 || read==-1);
        byteBuffer.flip();
        StringBuilder result = new StringBuilder();
        result.append(StandardCharsets.UTF_8.decode(byteBuffer));
        logger.info("服务器返回结果为:{}", result);
        byteBuffer.clear();
        socketChannel.close();
    }
}

运行结果

  • Server端
    在这里插入图片描述
  • Client端
    在这里插入图片描述

同步非阻塞模型如果在应用中实现的话,基本没有实际应用的机会。这个性能太差了。需要在应用中死循环read操作。在前面我们说过read操作涉及系统调用,本身就是一个重型操作,while(true){read()}只会让CPU风扇疯响。

多路复用IO

NIO2.0版本,通过selector在操作系统层面判断是否有可读的数据,性能得到了极大的提高。相比传统的BIO能更好的支持并发。多路复用IO适合连接数多且连接比较短的业务场景,比如大名鼎鼎的Redis就是使用多路复用IO。唯一的遗憾是代码写起来有点费脑子…
图片转自
在这里插入图片描述

NIO2Server

public class NIO2Server {
    private static final Logger logger = LoggerFactory.getLogger(NIO2Server.class);

    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        serverSocketChannel.bind(new InetSocketAddress(8080));
        logger.info("等待新连接");
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
        Map<SocketChannel, Object> resultMap = new HashMap<>();
        while (true) {
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocket = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel newSocket = serverSocket.accept();
                    newSocket.configureBlocking(false);
                    newSocket.register(selector, SelectionKey.OP_READ);
                    logger.info("获取到连接了{}", newSocket);
                } else if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    logger.info("有可读的连接了{}", socketChannel);
                    StringBuilder builder = new StringBuilder();
                    int read;
                    do {
                        read = socketChannel.read(byteBuffer);
                    } while (read == 0 || read == -1);
                    byteBuffer.flip();
                    builder.append(StandardCharsets.UTF_8.decode(byteBuffer));
                    byteBuffer.clear();
                    logger.info("读取到来自客户端的数据[{}]", builder);
                    Object result;
                    try {
                        result = AviatorEvaluator.execute(builder.toString());
                        logger.info("AviatorEvaluator的计算结果是[{}]", result);
                    } catch (Exception e) {
                        result = e.getMessage();
                    }
                    resultMap.put(socketChannel, result);
                    socketChannel.register(selector, SelectionKey.OP_WRITE);
                } else if (selectionKey.isWritable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    logger.info("有可写的连接了{}", socketChannel);
                    Object result = resultMap.remove(socketChannel);
                    if (result != null) {
                        byteBuffer.put(result.toString().getBytes(StandardCharsets.UTF_8));
                        byteBuffer.flip();
                        socketChannel.write(byteBuffer);
                        byteBuffer.clear();
                    }
                    socketChannel.close();
                }
                iterator.remove();
            }
        }
    }
}

其实就是比NIO1.0版本多了Selector,由Selector负责阻塞,当有感兴趣的事件到来时再放行。

NIO2Client

public class NIO2Client {
    private static final Logger logger = LoggerFactory.getLogger(NIO2Client.class);

    @Test
    public void test1() throws IOException {
        test("22+33+44+55");
    }
    private void test(String exp) throws IOException {
        logger.info("开始建立连接");
        long timeMillis = System.currentTimeMillis();
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        socketChannel.connect(new InetSocketAddress(8080));
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        while(true){
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                if (selectionKey.isConnectable()) {
                    logger.info("连接建立成功,耗时{}毫秒",System.currentTimeMillis()-timeMillis);
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    channel.configureBlocking(false);
                    channel.finishConnect();
                    channel.register(selector, SelectionKey.OP_WRITE);
                }else if(selectionKey.isWritable()){
                    logger.info("传入计算表达式:{}",exp);
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    byteBuffer.put(exp.getBytes(StandardCharsets.UTF_8));
                    byteBuffer.flip();
                    channel.write(byteBuffer);
                    byteBuffer.clear();
                    channel.register(selector, SelectionKey.OP_READ);
                }else if(selectionKey.isReadable()){
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    int read = channel.read(byteBuffer);
                    byteBuffer.flip();
                    StringBuilder builder = new StringBuilder();
                    builder.append(StandardCharsets.UTF_8.decode(byteBuffer));
                    logger.info("服务器返回结果为:{}", builder);
                    channel.close();
                    return;
                }
            }
        }
    }
}

与服务端版本不同的是,客户端的NIO2.0注册的是SelectionKey.OP_CONNECT事件,而服务端注册的是SelectionKey.OP_ACCEPT

运行结果

  • Server端
    在这里插入图片描述
  • Client端
    在这里插入图片描述

信号驱动IO

所谓信号驱动式I/O(signal-driven I/O),就是预先告知内核,当某个描述符准备发生某件事情的时候,让内核发送一个信号通知应用进程。信号驱动IO在java应用层面没有对应的API。

异步IO

号称是最完美的IO模型,异步非阻塞IO。JDK1.7以后才支持的,与NIO2.0不同的是,AIO不再需要Selector,而是通过回调的方式通知程序。可以让应用程序仅关注数据。一般适用于连接数较多而且连接时间较长的应用。但是代码写起来真是一言难尽…

需要额外注意的是,AIO需要操作系统的支持,linux上目前依旧是使用epoll来模拟的AIO,所以linux上的AIO与多路复用IO效率没啥本质区别。目前只有Windows平台真正从操作系统层面支持了AIO

图片转自
在这里插入图片描述

AIOServer

public class AIOServer {
    private static final Logger logger = LoggerFactory.getLogger(AIOServer.class);

    public static void main(String[] args) throws IOException {
        AIOServer aioServer = new AIOServer();
        AsynchronousServerSocketChannel serverSocketChannel = aioServer.init();
        aioServer.listen(serverSocketChannel);
        try {
            TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private AsynchronousServerSocketChannel init() throws IOException {
        ThreadPoolExecutor bizThreadPool = createBizThreadPool();
        AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(bizThreadPool);
        AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
        serverSocketChannel.bind(new InetSocketAddress(8080));
        return serverSocketChannel;
    }

    void listen(AsynchronousServerSocketChannel serverSocketChannel) throws IOException {
        logger.info("等待新连接");
        serverSocketChannel.accept(this, new CompletionHandler<AsynchronousSocketChannel, AIOServer>() {
            @Override
            public void completed(AsynchronousSocketChannel socketChannel, AIOServer attachment) {
                if (socketChannel.isOpen()) {
                    logger.info("获取到连接了{}", socketChannel);
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    socketChannel.read(byteBuffer, socketChannel, new CompletionHandler<Integer, AsynchronousSocketChannel>() {
                        @Override
                        public void completed(Integer result, AsynchronousSocketChannel channel) {
                            byteBuffer.flip();
                            StringBuilder builder = new StringBuilder();
                            builder.append(StandardCharsets.UTF_8.decode(byteBuffer));
                            byteBuffer.clear();
                            logger.info("读取到来自客户端的数据[{}]", builder);
                            Object calcResult;
                            try {
                                calcResult = AviatorEvaluator.execute(builder.toString());
                                logger.info("AviatorEvaluator的计算结果是[{}]", calcResult);
                            } catch (Exception e) {
                                calcResult = e.getMessage();
                            }
                            byteBuffer.put(calcResult.toString().getBytes(StandardCharsets.UTF_8));
                            byteBuffer.flip();
                            channel.write(byteBuffer, channel, new CompletionHandler<Integer, AsynchronousSocketChannel>() {
                                @Override
                                public void completed(Integer result, AsynchronousSocketChannel attachment) {
                                    logger.info("数据写入完毕{}", attachment);
                                    byteBuffer.clear();
                                    try {
                                        attachment.close();
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }

                                @Override
                                public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
                                    logger.error("write failed",exc);
                                }
                            });
                        }

                        @Override
                        public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
                            logger.error("read failed",exc);
                        }
                    });
                }
                try {
                    listen(serverSocketChannel);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, AIOServer attachment) {
                logger.error("accept failed",exc);
            }
        });
    }

    private ThreadPoolExecutor createBizThreadPool() {
        return new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, new SynchronousQueue<>(),
                new ThreadFactory() {
                    private final AtomicInteger atomicInteger = new AtomicInteger();

                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "AIOServer-" + atomicInteger.getAndIncrement());
                    }
                },
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        logger.info("当前没有可用的线程了,任务被挂起");
                        try {
                            executor.getQueue().put(r);
                            logger.info("任务成功入队");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
    }
}

回调地狱…

AIOClient

public class AIOClient {
    private static final Logger logger = LoggerFactory.getLogger(AIOClient.class);
    @Test
    public void test1() throws IOException {
        test("22+33+44+55");
    }
    private void test(String exp) throws IOException {
        logger.info("开始建立连接");
        CountDownLatch countDownLatch = new CountDownLatch(1);
        long timeMillis = System.currentTimeMillis();
        AsynchronousSocketChannel asynchronousSocketChannel = AsynchronousSocketChannel.open();
        asynchronousSocketChannel.connect(new InetSocketAddress("localhost",8080), asynchronousSocketChannel, new CompletionHandler<Void, AsynchronousSocketChannel>() {
            @Override
            public void completed(Void result, AsynchronousSocketChannel attachment) {
                logger.info("连接建立成功,耗时{}毫秒",System.currentTimeMillis()-timeMillis);
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                byteBuffer.put(exp.getBytes(StandardCharsets.UTF_8));
                byteBuffer.flip();
                attachment.write(byteBuffer, attachment, new CompletionHandler<Integer, AsynchronousSocketChannel>() {
                    @Override
                    public void completed(Integer result, AsynchronousSocketChannel attachment) {
                        logger.info("传入计算表达式:{}",exp);
                        byteBuffer.clear();
                        attachment.read(byteBuffer, attachment, new CompletionHandler<Integer, AsynchronousSocketChannel>() {
                            @Override
                            public void completed(Integer result, AsynchronousSocketChannel attachment) {
                                StringBuilder builder = new StringBuilder();
                                byteBuffer.flip();
                                builder.append(StandardCharsets.UTF_8.decode(byteBuffer));
                                countDownLatch.countDown();
                                logger.info("服务器返回结果为:{}", builder);
                                try {
                                    attachment.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }

                            @Override
                            public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
                                logger.error("数据读取失败",exc);
                            }
                        });
                    }

                    @Override
                    public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
                        logger.error("数据写入失败",exc);
                    }
                });
            }

            @Override
            public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
                logger.error("连接失败",exc);
            }
        });
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

依旧是回调地狱…

运行结果

  • Server端
    在这里插入图片描述
  • Client端
    在这里插入图片描述

虽然代码写起来有点别扭,但是从日志里可以看到AIO充分利用了多线程的优势,如果linux上更好的支持了AIO,那AIO绝对会成为应用服务IO模型的首选。

后记

  1. 从我个人的角度看,IO模型的选型更大的意义是存在于服务端的IO模型选型。客户端直接BIO的写法写就完事儿了,易读易维护。但是上面的代码依旧提供了每种IO模型对应的Client的写法,更多的是为了领会不同IO模型的精神而已。
  2. IO模型给我们提供的应该不仅仅是IO模型,因为大家都知道现在用NIO就是标准答案了,具体操作上使用netty也就就足够了。但是IO模型本质上是为解决两个操作速度不一致的问题的,在应用程序上也会存在两个或多个操作间效率不同的情况,当我们遇到这种情况时,可以考虑IO模型为我们提供的五种解决思路,我觉得这个才是IO模型能给我们带来的好处。
  3. demo中用到的计算引擎是aviator,具体的依赖在下面:
    implementation 'com.googlecode.aviator:aviator:5.3.0'
    
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值