zoukankan      html  css  js  c++  java
  • NIO原理剖析与Netty初步----浅谈高性能服务器开发(一)

        除特别注明外,本站所有文章均为原创,转载请注明地址 

        在博主不长的工作经历中,NIO用的并不多,由于使用原生的Java NIO编程的复杂性,大多数时候我们会选择Netty,mina等开源框架,但理解NIO的原理就不重要了吗?恰恰相反,理解NIO底层机制是理解这一切的基础,由此我总结一下当初学习NIO时的笔记,以便后续复习。

         以下是我理解的Java原生NIO开发大致流程:

        

           上图大致描述的是服务端的NIO操作。

    第一步,绑定一个服务的端口

           这与传统阻塞IO中的ServerSocket类似,没什么好说的

    第二步,打开通道管理器Selector并在Selector上注册一个事件

          当注册的事件发生时,Selector.select()会返回,否则一直阻塞。这一步很有意思,也是NIO第一个与传统IO不同的地方,NIO通过一个Selector线程可以管理大量客户端连接,反之传统IO一个客户端连接进来必须创建一个新的线程为它服务(当然你可以使用连接池),我们知道线程对服务端来说是十分宝贵的资源,一个服务端进程所包含的线程是有   限的;此外,每个线程会占用一定的内存空间,过多的线程可能导致内存溢出,这种情况下你可能会到想对虚拟机进行调优,比如通过修改参数-Xss限制单个线程大小,但这又可   能导致StackOverFlow;另外,线程调度需要切换上下文,对于操作系统,它需要通过TCB(线程控制块)来对线程进行调度,过多的上下文切换浪费了CPU时间,降低了系统效     率。

    第三步,轮循访问Selector,当注册的事件到达时,方法返回

       下面的代码可以看到,方法整体是一个死循环,轮询访问Selector,发生某些已经注册在Selector上的事件时,该方法返回。可以通过selector.selectedKeys获取发生的事件,

    该方法返回的是一个泛型集合Set<SelectionKey>,遍历这个集合与遍历普通集合没有什么不同,这里我们通过迭代器迭代的原因是我们需要删除已经处理的Key,避免重复处理:

    public void listen() throws IOException {// 轮询访问selector
            while (true) {
                // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
                selector.select();
                // 获得selector中选中的项的迭代器,选中的项为注册的事件
                Iterator<?> ite = this.selector.selectedKeys().iterator();
                while (ite.hasNext()) {
                    SelectionKey key = (SelectionKey) ite.next();
                    //删除已选的key,以防重复处理
                    ite.remove();
             //这里可以写我们自己的处理逻辑
                    handle(key);
                }
            }
    }

       在第二步时,已经在Selector上注册了Accept事件,当这里的selector.select()返回时,代表客户端已经可以连接了,在handle方法里可以处理这个事件:

    public void handle(SelectionKey key) throws IOException {
        // 客户端请求连接事件
        if (key.isAcceptable()){
    //从Key里可以很方便的取到注册这个事件的Channel

            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            // 获得和客户端连接的通道
            SocketChannel channel = server.accept();
            // 设置成非阻塞
            channel.configureBlocking(false);

            logger.info("客户端已经连接!");

            //客户端连接在通道管理器Selector上注册读事件

            channel.register(this.selector, SelectionKey.OP_READ);

        }
    }

        上面的代码很简单,我们通过key获取到注册它的那个Channel,在这里是ServerSocketChannel,通过server.accept()获取客户端连接,这里同样可以类比到传统的阻塞

    IO,在阻塞IO中我们可以通过ServerSocket.accept获取到socket,唯一不同的是,阻塞IO中的accept方法是阻塞操作,而NIO中是非阻塞的。

        当然,仅仅是连接到客户端并没有什么用处,服务端需要有读写数据的能力,比如你可以用NIO实现一个Http服务器(当然最佳实践使用Netty等框架)。所以我们需要在Selector

    上注册读事件,同样,当读事件发生时,执行我们自己的业务逻辑。下面是修改后的代码:

    public void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()){
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel channel = server.accept();
            channel.configureBlocking(false);
            logger.info("客户端已经连接!");
            channel.register(this.selector, SelectionKey.OP_READ);
        } else if(key.isReadable()){
            SocketChannel channel = (SocketChannel) key.channel();
            // 创建读取缓冲
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //读取到Buffer中
            int read = channel.read(buffer);
            if(read > 0){
                byte[] data = buffer.array();
                String msg = new String(data).trim();
                logger.info("receive msg: {}",msg)
                
                //回写数据
                ByteBuffer outBuffer = ByteBuffer.wrap("OK".getBytes());
                channel.write(outBuffer);
            }else{
                logger.info("client closed!!!");
                key.cancel();
            }
        }
    } 

    总结

         本文大致讲述了使用NIO进行服务器端开发的大致流程,但代码显然仍然存在问题,其一是我们只使用了一个线程执行所有操作,包括接收客户端连接,读取数据,返回数据,对于这个简单的Demo来说已经足够了,但在实际的服务器开发中,例如你想使用NIO开发自己的HTTP服务器,服务器本地需要做大量操作,包括解析用户请求,根据请求路由到某一个Action执行业务逻辑,这其中又很可能某些数据从数据库读取,渲染模板等操作,十分耗时,这无疑又称为系统的瓶颈,再者,使用单一线程不能充分利用多核CPU提供的计算能力。下一篇中会看到,在基于Reactor模型的Netty中,会使用一个Boss线程接收客户端请求,使用多个Worker线程执行具体的业务逻辑。

        

      

         

         

       

  • 相关阅读:
    用U3D寻找看电视的感觉!!
    MipMap
    什么是 A 轮融资?有 B轮 C轮么?
    Java写的斗地主游戏源码
    sqlserver sp_spaceused用法
    SQL中的全局变量和局部变量(@@/@)
    SQL2008数据库导出到SQL2000全部步骤过程
    生成Insert语句的存储过程
    物理机连接虚拟机数据库
    配置sql server 2000以允许远程访问
  • 原文地址:https://www.cnblogs.com/showing/p/6759653.html
Copyright © 2011-2022 走看看