zoukankan      html  css  js  c++  java
  • NIO、AIO、BIO是个啥?

    一、简介

    NIO

    一种同步非阻塞的I/O。

    AIO

    异步非阻塞I/O。

    BIO

    同步阻塞IO操作。

    二、名词解释

    阻塞和非阻塞

    当线程执行阻塞操作时,是只能等待,而不能执行其他事情的。
    非阻赛是不需要等待,直接返回,继续执行下一个操作。

    同步和异步

    同步异步是运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步。
    异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系。

    IO分类

    按操作数据分为:字节流(Reader、Writer)和字符流(InputStream、OutputStream)
    按流向分:输入流(Reader、InputStream)和输出流(Writer、OutputStream)

    三、实战演示

    BIO

    服务端代码:

    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    /**
     * @ProjectName: onereader
     * @Package: com.onereader.webblog.common.bio
     * @ClassName: BIOServer
     * @Author: onereader
     * @Description: ${description}
     * @Date: 2019/9/1 14:30
     * @Version: 1.0
     */
    public class BIOServer {
    
        ServerSocket server;
        //服务器
        public BIOServer(int port){
            try {
                //把Socket服务端启动
                server = new ServerSocket(port);
                System.out.println("BIO服务已启动,监听端口是:" + port);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 开始监听,并处理逻辑
         * @throws IOException
         */
        public void listener() throws IOException{
            //死循环监听
            while(true){
                //等待客户端连接,阻塞方法
                Socket client = server.accept();
                //获取输入流
                InputStream is = client.getInputStream();
                //定义数组,接收字节流
                byte [] buff = new byte[1024];
                int len = is.read(buff);
                //只要一直有数据写入,len就会一直大于0
                if(len > 0){
                    String msg = new String(buff,0,len);
                    System.out.println("收到" + msg);
                }
            }
        }
    
        public static void main(String[] args) throws IOException {
            new BIOServer(9999).listener();
        }
    }
    
    

    客户端代码:

    import java.io.OutputStream;
    import java.net.Socket;
    import java.util.concurrent.CountDownLatch;
    /**
     * @ProjectName: onereader
     * @Package: com.onereader.webblog.common.bio
     * @ClassName: BIOClient
     * @Author: onereader
     * @Description: ${description}
     * @Date: 2019/9/1 14:31
     * @Version: 1.0
     */
    public class BIOClient {
    
        public static void main(String[] args){
            int count = 10;
            //计数器,模拟10个线程
            final CountDownLatch latch = new CountDownLatch(count);
            for(int i = 0 ; i < count; i ++){
                new Thread(){
                    @Override
                    public void run() {
                        try{
                            //等待,保证线程一个一个创建连接
                            latch.await();
                            //创建socket,连接服务端
                            Socket client = new Socket("localhost", 9999);
                            //获取输出流
                            OutputStream os = client.getOutputStream();
                            //获取当前线程名
                            String name = "客户端线程:"+Thread.currentThread().getName();
                            //发送到服务端
                            os.write(name.getBytes());
                            //关闭输入流
                            os.close();
                            //关闭socket连接
                            client.close();
    
                        }catch(Exception e){
    
                        }
                    }
    
                }.start();
                //计数器减1
                latch.countDown();
            }
        }
    }
    

    结果:

    收到客户端线程:Thread-1
    收到客户端线程:Thread-4
    收到客户端线程:Thread-0
    收到客户端线程:Thread-3
    收到客户端线程:Thread-2
    收到客户端线程:Thread-6
    收到客户端线程:Thread-8
    收到客户端线程:Thread-7
    收到客户端线程:Thread-5
    收到客户端线程:Thread-9
    

    简单来说明以下,启动服务端后,服务端就会一直处于阻塞等待状态,等待客户端连接后,才能继续执行读取客户端发送的信息,然后在进入循环阻塞,等待新的连接处理新的信息数据。上面,我们创建了10个线程模拟是个客户端连接服务端发送信息,服务端在循环阻塞方式,接收到客户端传过来的信息。

    NIO

    NIO的三个主要组成部分:

    Channel(通道)、Buffer(缓冲区)、Selector(选择器)

    NIO执行流程:

    当客户端在使用NIO连接到服务端,先会打开Channel(通道),然后把数据写入到Buffer(缓冲区),并把Buffer数据通过通道发送,最后向选择器中注册一个事件。
    服务端则会先打开Channel(通道),然后监听选择器中的事件,如果发现有事件后,就从渠道中获取Buffer,然后从Buffer中读取数据。如果没有,则一直循环监听下去。

    Channel(通道)

    Channel是一个对象,可以通过它读取和写入数据。
    Channel是双向的,既可以读又可以写。
    Channel可以进行异步的读写。
    Channel的读写必须通过buffer对象。

    在Java NIO中的Channel主要有如下几种类型:
    FileChannel:从文件读取数据的
    DatagramChannel:读写UDP网络协议数据
    SocketChannel:读写TCP网络协议数据
    ServerSocketChannel:可以监听TCP连接

    Buffer(缓存区)

    Buffer是一个对象,它包含一些要写入或者读到Stream对象的。应用程序不能直接对 Channel 进行读写操作,而必须通过 Buffer 来进行,即 Channel 是通过 Buffer 来读写数据的。

    在NIO中,所有的数据都是用Buffer处理的,它是NIO读写数据的中转池。Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。但一个缓冲区不仅仅是一个数组,重要的是它提供了对数据的结构化访问,而且还可以跟踪系统的读写进程。

    Buffer属性介绍:

    容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一个容量在缓冲区创建时被设定,并且永远不能改变。
    上界(Limit):缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。
    位置(Position):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。
    标记(Mark):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。

    读写数据步骤:

    1.写入数据到 Buffer;
    2.调用 flip() 方法;
    3.从 Buffer 中读取数据;
    4.调用 clear() 方法或者 compact() 方法。

    当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip() 方法将 Buffer 从写模式切换到读模式。在读模式下,可以读取之前写入到 Buffer 的所有数据。

    一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear() 或 compact() 方法。clear() 方法会清空整个缓冲区。compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

    Buffer主要有如下几种:
    ByteBuffer
    CharBuffer
    DoubleBuffer
    FloatBuffer
    IntBuffer
    LongBuffer
    ShortBuffer

    Selector(选择器)

    无论客户端还是服务端都可以向Selector中注册事件,然后监听事件,最后根据不同的事件做不同的处理。

    下面看下代码
    客户端:

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.SocketChannel;
    import java.nio.charset.Charset;
    import java.util.Iterator;
    import java.util.Scanner;
    import java.util.Set;
    /**
     * @ProjectName: onereader
     * @Package: com.onereader.webblog.common.nio
     * @ClassName: NIOClient
     * @Author: onereader
     * @Description: ${description}
     * @Date: 2019/9/1 13:41
     * @Version: 1.0
     */
    public class NIOClient {
        private final InetSocketAddress serverAdrress = new InetSocketAddress("localhost", 9999);
        private Selector selector = null;
        private SocketChannel client = null;
        private Charset charset = Charset.forName("UTF-8");
    
        public NIOClient() throws IOException{
            //1.连接远程主机的IP和端口
            client = SocketChannel.open(serverAdrress);
            client.configureBlocking(false);
    
            //打开选择器,注册读事件
            selector = Selector.open();
            client.register(selector, SelectionKey.OP_READ);
        }
    
        public void session(){
            //开辟一个新线程从服务器端读数据
            new Reader().start();
            //开辟一个新线程往服务器端写数据
            new Writer().start();
        }
    
        /**
         * 写数据线程
         */
        private class Writer extends Thread{
    
            @Override
            public void run() {
                try{
                    //在主线程中 从键盘读取数据输入到服务器端
                    Scanner scan = new Scanner(System.in);
                    while(scan.hasNextLine()){
                        String line = scan.nextLine();
                        if("".equals(line)){
                            //不允许发空消息
                            continue;
                        }
                        //当前渠道是共用的,发送当前输入数据
                        client.write(charset.encode(line));
                    }
                    scan.close();
                }catch(Exception e){
    
                }
            }
    
        }
    
        /**
         * 读数据线程
         */
        private class Reader extends Thread {
            @Override
            public void run() {
                try {
                    //循环检测
                    while(true) {
                        int readyChannels = selector.select();
                        if(readyChannels == 0){
                            continue;
                        }
                        //获取selected所有的事件
                        Set<SelectionKey> selectedKeys = selector.selectedKeys();
                        Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
                        while(keyIterator.hasNext()) {
                            SelectionKey key = (SelectionKey) keyIterator.next();
                            keyIterator.remove();
                            process(key);
                        }
                    }
                }
                catch (IOException io){
    
                }
            }
    
            /**
             * 根据事件的不同,做不同的处理
             * @param key
             * @throws IOException
             */
            private void process(SelectionKey key) throws IOException {
                //读就绪事件
                if(key.isReadable()){
                    //通过key找到对应的通道
                    SocketChannel sc = (SocketChannel)key.channel();
                    //创建缓存区
                    ByteBuffer buff = ByteBuffer.allocate(1024);
                    String content = "";
                    //读数据
                    while(sc.read(buff) > 0){
                        buff.flip();
                        content += charset.decode(buff);
                    }
                    //打印内容
                    System.out.println(content);
                    //设置当前为读就绪
                    key.interestOps(SelectionKey.OP_READ);
                }
            }
        }
        
        public static void main(String[] args) throws IOException {
            new NIOClient().session();
        }
    }
    
    

    服务端:

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.nio.charset.Charset;
    import java.util.Iterator;
    import java.util.Set;
    
    /**
     * @ProjectName: onereader
     * @Package: com.onereader.webblog.common.nio
     * @ClassName: NIOServer
     * @Author: onereader
     * @Description: ${description}
     * @Date: 2019/9/1 13:23
     * @Version: 1.0
     */
    public class NIOServer {
    
        private int port = 9999;
        private Charset charset = Charset.forName("UTF-8");
        private Selector selector = null;
    
        public NIOServer(int port) throws IOException{
    
            this.port = port;
            //1.打开通道
            ServerSocketChannel server = ServerSocketChannel.open();
            //设置服务端口
            server.bind(new InetSocketAddress(this.port));
            server.configureBlocking(false);
            //2.打开选择器
            selector = Selector.open();
            //注册等待事件
            server.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务已启动,监听端口是:" + this.port);
        }
    
        /**
         *  监听事件
         * @throws IOException
         */
        public void listener() throws IOException{
            //死循环,这里不会阻塞
            while(true) {
                //1.在轮询获取待处理的事件
                int wait = selector.select();
                System.out.println("当前等待处理的事件:"+wait+"个");
                if(wait == 0){
                    //如果没有可处理的事件,则跳过
                    continue;
                }
                //获取所有待处理的事件
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = keys.iterator();
                //遍历
                while(iterator.hasNext()) {
                    SelectionKey key = (SelectionKey) iterator.next();
                    //处理前,关闭选在择器中的事件
                    iterator.remove();
                    //处理事件
                    process(key);
                    System.out.println("事件Readable:"+key.isReadable());
                    System.out.println("事件Acceptable:"+key.isAcceptable());
                }
            }
    
        }
    
        /**
         * 根据事件类型,做处理
         * @param key
         * @throws IOException
         */
        public void process(SelectionKey key) throws IOException {
            //连接就绪
            if(key.isAcceptable()){
                //获取通道
                ServerSocketChannel server = (ServerSocketChannel)key.channel();
                //进入服务端等待
                SocketChannel client = server.accept();
                //非阻塞模式
                client.configureBlocking(false);
                //注册选择器,并设置为读取模式,收到一个连接请求,
                // 然后起一个SocketChannel,并注册到selector上,
                // 之后这个连接的数据,就由这个SocketChannel处理
                client.register(selector, SelectionKey.OP_READ);
                //将此对应的channel设置为准备接受其他客户端请求
                key.interestOps(SelectionKey.OP_ACCEPT);
                client.write(charset.encode("来自服务端的慰问"));
            }
            //读就绪
            if(key.isReadable()){
                //返回该SelectionKey对应的 Channel,其中有数据需要读取
                SocketChannel client = (SocketChannel)key.channel();
    
                //往缓冲区读数据
                ByteBuffer buff = ByteBuffer.allocate(1024);
                StringBuilder content = new StringBuilder();
                try{
                    while(client.read(buff) > 0){
                        buff.flip();
                        content.append(charset.decode(buff));
                    }
                    System.out.println("接收到客户端:"+content.toString());
                    //将此对应的channel设置为准备下一次接受数据
                    key.interestOps(SelectionKey.OP_READ);
                }catch (IOException io){
                    key.cancel();
                    if(key.channel() != null){
                        key.channel().close();
                    }
                }
    
            }
        }
    
        public static void main(String[] args) throws IOException {
            new NIOServer(9999).listener();
        }
    
    
    }
    
    

    结果:

    客户端:

    来自服务端的慰问
    请输入要发送服务端的信息:
    你好呀
    
    

    服务端:

    服务已启动,监听端口是:9999
    当前等待处理的事件:1个
    事件Readable:false
    事件Acceptable:true
    当前等待处理的事件:1个
    接收到客户端:你好呀
    事件Readable:true
    事件Acceptable:false
    
    

    这里配合上面的基础知识介绍,然后看代码就能明白。整个过程了~

    AIO

    客户端:

    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.AsynchronousSocketChannel;
    import java.nio.channels.CompletionHandler;
    import java.util.concurrent.CountDownLatch;
    /**
     * @ProjectName: onereader
     * @Package: com.onereader.webblog.common.aio
     * @ClassName: AIOClient
     * @Author: onereader
     * @Description: ${description}
     * @Date: 2019/9/1 14:03
     * @Version: 1.0
     */
    public class AIOClient {
    
        private final AsynchronousSocketChannel client ;
    
        public AIOClient() throws Exception{
    
            client = AsynchronousSocketChannel.open();
        }
    
        public void connect(String host,int port)throws Exception{
    
            //连接服务端
            client.connect(new InetSocketAddress(host,port),null,new CompletionHandler<Void,Void>() {
    
                /**
                 * 成功操作
                 * @param result
                 * @param attachment
                 */
                @Override
                public void completed(Void result, Void attachment) {
                    try {
                        client.write(ByteBuffer.wrap(("客户端线程:" + Thread.currentThread().getName()+"请求服务端").getBytes())).get();
                         System.out.println("已发送至服务器");
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
    
                /**
                 * 失败操作
                 * @param exc
                 * @param attachment
                 */
                @Override
                public void failed(Throwable exc, Void attachment) {
                    exc.printStackTrace();
                }
            });
    
            //读取数据
            final ByteBuffer bb = ByteBuffer.allocate(1024);
            client.read(bb, null, new CompletionHandler<Integer,Object>(){
    
                        /**
                         * 成功操作
                         * @param result
                         * @param attachment
                         */
                        @Override
                        public void completed(Integer result, Object attachment) {
                            System.out.println("获取反馈结果:" + new String(bb.array()));
                        }
    
                        /**
                         * 失败操作
                         * @param exc
                         * @param attachment
                         */
                        @Override
                        public void failed(Throwable exc, Object attachment) {
                            exc.printStackTrace();
                        }
                    }
            );
    
    
            try {
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException ex) {
                System.out.println(ex);
            }
    
        }
    
        public static void main(String args[])throws Exception{
            int count = 10;
            final CountDownLatch latch = new CountDownLatch(count);
    
            for (int i = 0; i < count; i ++) {
                latch.countDown();
                new Thread(){
                    @Override
                    public void run(){
                        try{
                            latch.await();
                            new AIOClient().connect("localhost",9999);
                        }catch(Exception e){
    
                        }
                    }
                }.start();
            }
    
            Thread.sleep(1000 * 60 * 10);
        }
    
    
    }
    

    服务端:

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.AsynchronousChannelGroup;
    import java.nio.channels.AsynchronousServerSocketChannel;
    import java.nio.channels.AsynchronousSocketChannel;
    import java.nio.channels.CompletionHandler;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ExecutorService;
    
    import static java.util.concurrent.Executors.*;
    
    /**
     * @ProjectName: onereader
     * @Package: com.onereader.webblog.common.aio
     * @ClassName: AIOServer
     * @Author: onereader
     * @Description: ${description}
     * @Date: 2019/9/1 14:04
     * @Version: 1.0
     */
    public class AIOServer {
    
    
        private final int port;
    
        public static void main(String args[]) {
            int port = 9999;
            new AIOServer(port);
        }
    
        /**
         * 注册一个端口,用来给客户端连接
         * @param port
         */
        public AIOServer(int port) {
            this.port = port;
            listen();
        }
    
        //侦听方法
        private void listen() {
            try {
                //线程缓冲池,为了体现异步
                ExecutorService executorService = newCachedThreadPool();
                //给线程池初始化一个线程
                AsynchronousChannelGroup threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
    
                //Asynchronous异步
                final AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(threadGroup);
    
                //启动监听
                server.bind(new InetSocketAddress(port));
                System.out.println("服务已启动,监听端口" + port);
    
                final Map<String,Integer> count = new ConcurrentHashMap<String, Integer>();
                count.put("count", 0);
                //开始等待客户端连接
                //实现一个CompletionHandler 的接口,匿名的实现类
                server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                    //缓存区
                    final ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
                    //实现IO操作完成的方法
                    @Override
                    public void completed(AsynchronousSocketChannel result, Object attachment) {
                        count.put("count", count.get("count") + 1);
                        System.out.println(count.get("count"));
                        try {
                            //清空缓存标记
                            buffer.clear();
                            //读取缓存内容
                            result.read(buffer).get();
                            //写模式转换成读模式
                            buffer.flip();
                            result.write(buffer);
                            buffer.flip();
                        } catch (Exception e) {
                            System.out.println(e.toString());
                        } finally {
                            try {
                                result.close();
                                server.accept(null, this);
                            } catch (Exception e) {
                                System.out.println(e.toString());
                            }
                        }
                    }
    
                    //实现IO操作失败的方法
                    @Override
                    public void failed(Throwable exc, Object attachment) {
                        System.out.println("IO操作是失败: " + exc);
                    }
                });
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException ex) {
                    System.out.println(ex);
                }
            } catch (IOException e) {
                System.out.println(e);
            }
        }
    
    
    }
    
    

    结果:

    客户端

    已发送至服务器
    已发送至服务器
    已发送至服务器
    已发送至服务器
    已发送至服务器
    已发送至服务器
    已发送至服务器
    已发送至服务器
    已发送至服务器
    已发送至服务器
    获取反馈结果:客户端线程:Thread-22请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    获取反馈结果:客户端线程:Thread-21请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    获取反馈结果:客户端线程:Thread-20请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    获取反馈结果:客户端线程:Thread-19请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    获取反馈结果:客户端线程:Thread-18请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    获取反馈结果:客户端线程:Thread-17请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    获取反馈结果:客户端线程:Thread-16请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    获取反馈结果:客户端线程:Thread-15请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    获取反馈结果:客户端线程:Thread-14请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    获取反馈结果:客户端线程:Thread-13请求服务端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    

    服务端

    服务已启动,监听端口9999
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    

    总结

    看看代码实现,是不是比看一堆文字要舒服多了,我这里也不想过多熬诉了,大家自己看代码吧~
    以上就是我本次总结的,说实话看了很多大佬写的东西。实在是难以通过文字就明白这个东西是咋回事,最后还是亲手实践操作以下,才能理解。

  • 相关阅读:
    Vue项目中使用Vue-Quill-Editor富文本编辑器插件
    Element-UI中的Cascader 级联选择器高度以及位置问题
    Sublime中同一个文件进行分屏显示
    Oracle的clob数据类型
    查看Nginx版本号的几种方式
    华为路由器EasyNAT&NAT Server
    huawei路由器NAT配置
    15
    14
    13
  • 原文地址:https://www.cnblogs.com/one-reader/p/11469554.html
Copyright © 2011-2022 走看看