• Java NIO使用及原理之--选择器Seclector


    Java NIO使用及原理之--选择器Seclector

    本文转载自:李会军•宁静致远,供自己学习使用

    在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O。通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据。同样,写入调用将会阻塞直至数据能够写入。传统的Server/Client模式会基于TPR(Thread per Request),服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求。这种模式带来的一个问题就是线程数量的剧增,大量的线程会增大服务器的开销。大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池线程的最大数量,这由带来了新的问题,如果线程池中有200个线程,而有200个用户都在进行大文件下载,会导致第201个用户的请求无法及时处理,即便第201个用户只想请求一个几KB大小的页面。传统的 Server/Client模式如下图所示:

    NIO中非阻塞I/O采用了基于Reactor反应器模式(或者说观察者模式)的工作方式,I/O调用不会被阻塞,相反是注册感兴趣的特定I/O事件,如可读数据到达,新的套接字连接等等,在发生特定事件时,系统再通知我们。NIO中实现非阻塞I/O的核心对象就是Selector,Selector就是注册各种I/O事件地 方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件,如下图所示:

    从图中可以看出,当有读或写等任何注册的事件发生时,可以从Selector中获得相应的SelectionKey,同时从 SelectionKey中可以找到发生的事件和该事件所发生的具体的SelectableChannel,以获得客户端发送过来的数据。

    使用NIO中非阻塞I/O编写服务器处理程序,大体上可以分为下面三个步骤:

    1. 向Selector对象注册感兴趣的事件 
    2. 从Selector中获取感兴趣的事件 
    3. 根据不同的事件进行相应的处理

    接下来我们用一个简单的示例来说明整个过程。首先是向Selector对象注册感兴趣的事件:

     1 /*
     2  * 注册事件
     3  * */
     4 protected Selector getSelector() throws IOException {
     5     // 创建Selector对象
     6     Selector sel = Selector.open();
     7     
     8     // 创建可选择通道,并配置为非阻塞模式
     9     ServerSocketChannel server = ServerSocketChannel.open();
    10     server.configureBlocking(false);
    11     
    12     // 绑定通道到指定端口
    13     ServerSocket socket = server.socket();
    14     InetSocketAddress address = new InetSocketAddress(port);
    15     socket.bind(address);
    16     
    17     // 向Selector中注册感兴趣的事件
    18     server.register(sel, SelectionKey.OP_ACCEPT); 
    19     return sel;
    20 }

    创建了ServerSocketChannel对象,并调用configureBlocking()方法,配置为非阻塞模式,接下来的三行代码把该通道绑定到指定端口,最后向Selector中注册事件,此处指定的是参数是OP_ACCEPT,即指定我们想要监听accept事件,也就是新的连接发 生时所产生的事件,对于ServerSocketChannel通道来说,我们唯一可以指定的参数就是OP_ACCEPT。

    从Selector中获取感兴趣的事件,即开始监听,进入内部循环:

     1 /*
     2  * 开始监听
     3  * */ 
     4 public void listen() { 
     5     System.out.println("listen on " + port);
     6     try { 
     7         while(true) { 
     8             // 该调用会阻塞,直到至少有一个事件发生
     9             selector.select(); 
    10             Set<SelectionKey> keys = selector.selectedKeys();
    11             Iterator<SelectionKey> iter = keys.iterator();
    12             while (iter.hasNext()) { 
    13                 SelectionKey key = (SelectionKey) iter.next(); 
    14                 iter.remove(); 
    15                 process(key); 
    16             } 
    17         } 
    18     } catch (IOException e) { 
    19         e.printStackTrace();
    20     } 
    21 }

    在非阻塞I/O中,内部循环模式基本都是遵循这种方式。首先调用select()方法,该方法会阻塞,直到至少有一个事件发生,然后再使用selectedKeys()方法获取发生事件的SelectionKey,再使用迭代器进行循环。

    最后一步就是根据不同的事件,编写相应的处理代码:

     1 /*
     2  * 根据不同的事件做处理
     3  * */
     4 protected void process(SelectionKey key) throws IOException{
     5     // 接收请求
     6     if (key.isAcceptable()) {
     7         ServerSocketChannel server = (ServerSocketChannel) key.channel();
     8         SocketChannel channel = server.accept();
     9         channel.configureBlocking(false);
    10         channel.register(selector, SelectionKey.OP_READ);
    11     }
    12     // 读信息
    13     else if (key.isReadable()) {
    14         SocketChannel channel = (SocketChannel) key.channel(); 
    15         int count = channel.read(buffer); 
    16         if (count > 0) { 
    17             buffer.flip(); 
    18             CharBuffer charBuffer = decoder.decode(buffer); 
    19             name = charBuffer.toString(); 
    20             SelectionKey sKey = channel.register(selector, SelectionKey.OP_WRITE); 
    21             sKey.attach(name); 
    22         } else { 
    23             channel.close(); 
    24         } 
    25         buffer.clear(); 
    26     }
    27     // 写事件
    28     else if (key.isWritable()) {
    29         SocketChannel channel = (SocketChannel) key.channel(); 
    30         String name = (String) key.attachment(); 
    31         
    32         ByteBuffer block = encoder.encode(CharBuffer.wrap("Hello " + name)); 
    33         if(block != null)
    34         {
    35             channel.write(block);
    36         }
    37         else
    38         {
    39             channel.close();
    40         }
    41  
    42      }
    43 }

    此处分别判断是接受请求、读数据还是写事件,分别作不同的处理。

  • 相关阅读:
    Redis 错误摘记篇
    搭建备份到业务迁移---mysql
    业务迁移---web
    业务迁移---redis
    redis 编译安装错误问题
    location 匹配规则 (NGINX)
    nginx虚拟目录实现两个后台使用
    零基础学python-19.10 生成器是单迭代器
    零基础学python-19.9 生成器函数与生成器表达式
    零基础学python-19.8 生成器表达式:当迭代器遇上列表解析
  • 原文地址:https://www.cnblogs.com/javadevelper/p/9501429.html
Copyright © 2020-2023  润新知