美文网首页
NIO基础系列(一)

NIO基础系列(一)

作者: 维特无忧堡 | 来源:发表于2018-07-02 18:49 被阅读0次

    前言

    在讲解IO操作之前,我们可以回顾一下同步与异步、阻塞与非阻塞的概念

    • 同步:即有序,后一个操作必须等待前一个操作完成之后才能执行,所以说单线程执行的情况下一定是同步的;

    • 异步:不一定有序,后一个任务不必等前一个任务执行完成,比如说Android客户端编程的时候,你要访问一张网络图片,这时候你就会开启一个子线程去获取,UI线程继续往后面执行,等到获取完成之后再回调更新,这样大大加快了程序的执行速度,当然,这样说是指多核处理器的情况,如果是单个CPU的话这样处理速度会更慢是,因为线程切换也是很耗时的(切换上下文应该是重量级的操作吧);所以说实现异步的操作就是多线程

    • 阻塞:就是字面上的意思,当当前任务进行阻塞操作的时候,就会使得线程阻塞在这里,只有当条件就绪后才能继续,例如说ServerSocket.accept就是一个阻塞方法,不断的接受Socket的连接,当有个新的Socket连接时,accept才会往后面执行,还有常见的IO操作也是阻塞的;

    • 非阻塞: 不管任务是否结束,直接返回,相应操作在后台继续执行,异步IO就是这样。

    看到这里是不是感觉异步和非阻塞很像啊,我之前也是一脸蒙蔽,反正我这这样理解的:异步就是你把某件事交给别人去做,然后别人之后会主动告诉你做完了,而非阻塞的就是你做到这里时发现没做完,返回一个没做完的标记,然后继续往后面执行,等你下次再来,发现它做完了,就可以用了。

    NIO原理

    回到正题,NIO使用的是 多路复用技术 (select模式)
    把读写事件交给一个单独的线程来处理,这个线程完成IO事件 的注册功能,还有不断的去轮询我们的读写缓存区,看是否有数据准备好,准备好的话就通知相应的读写事件(线程),这样的话以前的读写线程就可以做其他的事,这个阻塞的不是所有的IO线程 阻塞的是select这个线程,这样就实现了复用

    一些具体的NIO概念我就不描述了,网上有很多,我只说一下主要的流程,让大家有个印象:整个处理流程是


    image.png

    分布演示

    放放代码让大家感受一下

    首先初始化

         * 服务端初始化操作
         */
        private void init(){
            try {
                ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                ServerSocket serverSocket = serverSocketChannel.socket();
                serverSocket.bind(new InetSocketAddress(PORT));//绑定端口
                serverSocketChannel.configureBlocking(false);//设置非阻塞
                selector = Selector.open();//通过默认的SelectorProvider对象获取一个新的实例
                selectionKey = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); //把服务端channel注册到选择器
                System.out.println("Server start :port at "+PORT);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    这里一定要设置非阻塞啊,如果你设置阻塞的话就没什么意义了,那么读写操作只能等到操作完才返回,这样就违背了呀

    然后事件轮询监听

     /**
         * 不断的监听客户端的连接,轮询选择器
         */
        @Override
        public void run() {
            while (true){
                try {
                    int count = selector.select();  //获取就绪channel
                    if (count == 0)  continue;
                    Iterator<SelectionKey> iterator =  selector.selectedKeys().iterator();
                    while(iterator.hasNext()){
                            SelectionKey selectionKey = iterator.next();
                            handleKey(selectionKey);
                            iterator.remove();  //把事件已经出去来了,然后把他删除掉
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    

    接下来处理 事件

    private void handleKey(SelectionKey key) throws IOException {
            ServerSocketChannel server = null;
            if (key.isAcceptable()){
                server = (ServerSocketChannel) key.channel();
                accept(server);
            }
            if (key.isValid() && key.isReadable()){
               readMsg(key);
            }
            if(key.isValid() && key.isWritable()){
                writeMsg(key);
            }
        }
    

    如果是读事件的话,你就可以从读管道中读取数据

       client = (SocketChannel) key.channel();
    
            //设置buffer缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(BLOCK_SIZE);
            //将数据读到缓冲区,然后把缓冲区的数据取出来
            int readByte = client.read(buffer);
            StringBuffer buf = new StringBuffer();
            //如果读取到了数据
            System.out.println("size = "+readByte);
    
    如果是写事件的话,你可以把相应的数据写入写管道
    

    channel.write(ByteBuffer.wrap(attachment.toString().getBytes()));

    在下一篇我将展示一个简易的聊天demo及遇到的问题

    相关文章

      网友评论

          本文标题:NIO基础系列(一)

          本文链接:https://www.haomeiwen.com/subject/iayzeftx.html