简介

  • 使用Selector(选择器), 可以使用一个线程处理多个客户端连接。
  • Selector 能够检测多个注册的通道上是否有事件发生(多个Channel以事件的方式可以注册到同一个Selector), 如果有事件发生, 便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道, 也就是管理多个连接和请求。
  • 只有在连接有读写事件发生时, 才会进行续写, 就大大地减少了系统开销, 并且不必为每个连接都创建一个线程, 不用去维护多个线程。
  • 避免了多线程之间的上下文切换导致的开销

特性

  • Netty的IO线程NioEventLoop聚合了Selector(选择器, 也被称为多路复用器), 可以同时并发处理成百上千个客户端连接。
  • 当线程从某客户端Socket通道进行读写数据时, 若没有数据可用时, 该线程可以进行其他任务。
  • 线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作, 所以单独的线程可以管理多个输入和输出通道。
  • 由于读写操作都是非阻塞的(Buffer), 这就可以充分提升IO线程的运行效率, 避免由于频繁I/O阻塞导致的线程挂起。
  • 一个I/O线程可以并发处理N个客户端连接和读写操作, 这从根本上解决了传统BIO一连接一线程模型, 架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

Selector类相关方法

  • public static Selector open(){}: 得到一个选择器对象

     /**
    * Opens a selector.
    *
    * <p> The new selector is created by invoking the {@link
    * java.nio.channels.spi.SelectorProvider#openSelector openSelector} method
    * of the system-wide default {@link java.nio.channels.spi.SelectorProvider}
    * object. </p>
    * 新的选择器通过调用系统全局默认对象SelectorProvider的openSelector方法来创建新
    * Selector对象
    *
    * @return A new selector
    *
    * @throws IOException
    * If an I/O error occurs
    */
    public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
    }
    • 在这里使用了Provider(提供者)设计模式, 通过provider方法提供一个选择器提供者对象, 该对象再调用openSelector()方法生成了一个Selector。

          /**
      * Returns the system-wide default selector provider for this invocation
      * of the Java virtual machine.
      * 为进行此次申请的Java虚拟机返回系统全局默认的选择器提供者
      *
      * <p> The first invocation of this method locates the default provider
      * object as follows: </p>
      * 第一次请求该方法定位了默认的提供者对象如下:
      * <ol>
      *
      * <li><p> If the system property
      * <tt>java.nio.channels.spi.SelectorProvider</tt> is defined then it
      * is taken to be the fully-qualified name of a concrete provider
      * class.
      * 如果系统所有物(选择提供者)被定义了, 那么它就会被认为是一个实体提供者类的完
      * 全限定名
      * The class is loaded and instantiated; if this process fails then an
      * unspecified error is thrown. </p></li>
      * 该类被加载并被初始化, 如果此过程失败了就会抛出 未定的错误
      *
      * <li><p> If a provider class has been installed in a jar file that is
      * visible to the system class loader, and that jar file contains a
      * provider-configuration file named
      * <tt>java.nio.channels.spi.SelectorProvider</tt> in the resource
      * directory <tt>META-INF/services</tt>, then the first class name
      * specified in that file is taken.
      * 如果提供者类已经被存入一个对系统类加载器可见的jar文件, 并且该jar文件包含了
      * 一个在 META-INF/services 资源目录下的 名为 SelectorProvider的提供者配置
      * 文件, 那么该文件中的第一个被列出的类名称被选择。
      *
      * The class is loaded and instantiated; if this process fails then an
      * unspecified error is thrown. </p></li>
      * 该类会被加载并且初始化, 如果此过程失败了就会抛出 未定的错误。
      *
      * <li><p> Finally, if no provider has been specified by any of the
      * above means then the system-default provider class is instantiated
      * and the result is returned. </p></li>
      * 最终, 如果没有提供者被任何以上方式定义, 那么系统默认提供者类就会被初始化,
      * 并返回该结果
      *
      * </ol>
      *
      * <p> Subsequent invocations of this method return the provider that was
      * returned by the first invocation. </p>
      * 接下来的对该方法的请求返回第一次请求时被返回的提供者。
      *
      * @return The system-wide default selector provider
      */
      public static SelectorProvider provider() {
      // 这个lock是个对象, 不是锁哦, synchronized锁的只是Object
      synchronized (lock) {
      // 如果提供者不为空, 就返回提供者本身
      if (provider != null)
      return provider;
      // 否则就调用获取控制器类的本地方法doPrivilege(给予权限)
      return AccessController.doPrivileged(
      // PrivilegedAction本身是一个接口, 实现该接口的类都要重写run方法
      new PrivilegedAction<SelectorProvider>() {
      public SelectorProvider run() {
      // 如果是以属性的方式加载(方法底层比较复杂, 反射, 类加载器, 迭代器都有涉及)
      if (loadProviderFromProperty())
      return provider;
      // 如果是以服务的方式加载(方法底层比较复杂, 同上)
      if (loadProviderAsService())
      return provider;
      // 创建了一个新的提供者对象
      provider = sun.nio.ch.DefaultSelectorProvider.create();
      return provider;
      }
      });
      }
      }
  • public int select(long timeout){}: 监控所有注册的通道, 当其中有IO操作可以进行时, 将对应的SelectionKey 加入到内部集合中并返回, 参数用来设置超时时间

    • 这是SelectorImpl实现类中的反编译代码
        public int select(long var1) throws IOException {
    // timeout < 0 就抛非法参数异常
    if (var1 < 0L) {
    throw new IllegalArgumentException("Negative timeout");
    } else {
    // 如果timeout时间为0, 就执行lockAndDoSelect(-1L), 否则就执行lockAndDoSelect(原timeout时间)
    return this.lockAndDoSelect(var1 == 0L ? -1L : var1);
    }
    } // 不传参默认为0L(无延迟)
    public int select() throws IOException {
    return this.select(0L);
    } // 加锁并选择(无延迟)
    public int selectNow() throws IOException {
    return this.lockAndDoSelect(0L);
    }
    private int lockAndDoSelect(long var1) throws IOException {
    // 锁了当前对象
    synchronized(this) {
    // 如果当前选择器没有打开, 就抛出选择器关闭异常
    if (!this.isOpen()) {
    throw new ClosedSelectorException();
    } else {
    int var10000;
    // 这其实是一个双重检查锁的单例模式
    // 对当前选择器的publickeys加锁
    synchronized(this.publicKeys) {
    // 对当前选择器的publicSelectedKeys加锁
    synchronized(this.publicSelectedKeys) {
    var10000 = this.doSelect(var1);
    }
    }
    return var10000;
    }
    }
    }
  • public Set selectedKeys(){}: 从内部集合中得到所有的SelectionKey

    public Set<SelectionKey> selectedKeys() {
    // 如果没有被打开并且工具类的bug级别为1.4, 就抛出异常
    if (!this.isOpen() && !Util.atBugLevel("1.4")) {
    throw new ClosedSelectorException();
    } else {
    // 否则就返回当前选择器的publicSelectedKeys
    return this.publicSelectedKeys;
    }
    }

Selector示意图

  • 说明:

    • 当客户端连接时, 会通过ServerSocketChannel得到SocketChannel
    • 将socketChannel注册到Selector上, register(Selector selector, int ops), 一个selector上可以注册多个SocketChannel
    • 注册后返回一个SelectionKey, 会与该Selector关联(集合)
    • Selector 进行监听 select方法, 返回有事件发生的通道的个数
    • 进一步得到各个 SelectionKey(发生的事件)
    • 再通过Selectionkey 反向获取 SocketChannel, 方法channel()
    • 可以通过得到的channel, 完成业务处理

服务端Demo

package com.ronnie.nio;

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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set; public class NIOServer {
public static void main(String[] args) throws IOException {
// 创建ServerSocketChannel -> ServerSocket ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 得到Selector对象
Selector selector = Selector.open(); // 绑定一个端口:8888, 在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(8888)); // 设置为非阻塞
serverSocketChannel.configureBlocking(false); // 把 serverSocketChannel注册到 selector, 连接事件为OP_ACCEPTOR
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 循环等待客户端连接
while (true){
// 无事件发生, 等待1秒
if(selector.select(1000) == 0){
System.out.println("Server waited for 1 second, no connection");
continue;
} /*如果返回的>0, 就获取到相关的selectionKey集合
1. 如果返回的>0, 表示已经获取到关注的事件了
2. selector.selectedKeys()返回关注事件的集合
通过 selectionKeys反向获取通道
*/ Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 遍历 Set<SelectionKey>, 使用迭代器遍历(iterator)
Iterator<SelectionKey> keyIterator = selectionKeys.iterator(); while (keyIterator.hasNext()){
// 获取到selectionKey
SelectionKey key = keyIterator.next(); // 根据key对应的通道发生的事件做相应的处理
if (key.isAcceptable()){ // 如果是OP_ACCEPT(有新的客户端连接)
// 为该客户端生成一个SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept(); // 将当前的 socketChannel 注册到 selector, 关注事件为OP_READ, 同时给socketChannel关联一个Buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (key.isReadable()){ // 发生OP_READ
// 通过key反向获取到对应的channel
SocketChannel channel = (SocketChannel) key.channel(); // 获取到该channel关联的buffer
ByteBuffer buffer = (ByteBuffer) key.attachment(); channel.read(buffer); System.out.println("From Client: " + new String(buffer.array()));
}
// 手动从集合中移除当前的selectionKey, 防止重复操作
keyIterator.remove();
}
} }
}

客户端Demo

package com.ronnie.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel; public class NIOClient {
public static void main(String[] args) throws IOException { // 得到一个网络通道
SocketChannel socketChannel = SocketChannel.open(); // 设置非阻塞模式
socketChannel.configureBlocking(false); // 提供服务器端ip 和 端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888); // 连接服务器
if (!socketChannel.connect(inetSocketAddress)){ while (!socketChannel.finishConnect()){
System.out.println("Need time to connect, the client side won't block, we can do other works");
}
}
// 如果连接成功, 就发送数据
String str = "Hello, Hadoop, Storm, Spark, Flink"; // wraps a byte array into a buffer
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes()); // 发送数据, 将buffer数据写入 channel
socketChannel.write(buffer);
System.in.read();
}
}

NIO组件 Selector(选择器)的更多相关文章

  1. NIO组件Selector调用实例

    *对于nio的非阻塞I/O操作,使用Selector获取哪些I/O准备就绪,注册的SelectionKey集合记录关联的Channel这些信息.SelectionKey记录Channel对buffer ...

  2. NIO组件Selector工作机制详解(上)

    转自:http://blog.csdn.net/haoel/article/details/2224055 一.  前言 自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但 ...

  3. NIO组件Selector详解

    Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件.这样,一个单独的线程可以管理多个channel,从而管理多个网络连接. 下面是 ...

  4. NIO组件Selector工作机制详解(下)

    转自:http://blog.csdn.net/haoel/article/details/2224069 五.  迷惑不解 : 为什么要自己消耗资源? 令人不解的是为什么我们的Java的New I/ ...

  5. NIO三大组件之Selector选择器

    什么是选择器 选择器的作用是完成IO的多路复用.一个通道代表一条连接通路,通过选择器可以同时监控多个通道的IO(输入输出)状况.选择器和通道的关系,是监控和被监控的关系. 使用 重要的成员 Selec ...

  6. Java NIO之Selector(选择器)

    历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) Java NIO 之 Channel(通道) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂re ...

  7. NIO(三):Selector选择器

    一.堵塞式与非堵塞式 在传统IO中,将数据由当前线程从客户端传入服务端,由服务端的内核进行判断传过来的数据是否合法,内核中是否存在数据. 如果不存在数据 ,并且数据并不合法,当前线程将会堵塞等待.当前 ...

  8. NIO的Selector

    参考自 Java NIO系列教程(六) Selector Java-NIO-Selector java.nio.channels.Selector NIO新功能Top 10(下) 出发点: 如何管理多 ...

  9. Android selector选择器的使用

    通常按钮在点击前和后有两种状态,比如点击前为蓝色,点击后为灰色,且不再响应点击事件. 如果不使用selector选择器,点击后,就需要在程序中进行以下的类似操作 button1.setBackgrou ...

随机推荐

  1. 凤凰系统(Phoenix OS)PC版安装,电脑上体验功能丰富的安卓系统

    PC版(X86版)ISO镜像下载地址:http://www.phoenixos.com/download_x86 下载完成后,可按照官方给出的安装教程进行安装. 凤凰系统帮助中心:http://www ...

  2. Linux运维工程师简历项目经验

    如何做好一个合格的运工程师,运维工程师前景怎么样呢?就这些问题,与大家交流一下.首先对于运维工程师的要求是十分严苛的了,运维工程师不但要针对不同的问题做出响应,而且需要不断的补充自己的知识面,并不继提 ...

  3. Controller层注解

    /** * Copyright © 2012-2014 <a href="https://github.com/thinkgem/jeesite">JeeSite< ...

  4. 「Luogu P3680 凸轮廓线」

    一道神奇的计算几何题 前置芝士 正三角形,正方形,圆:什么,您都会,那真是太好了. 三角函数的运用:因为我不是很想在这一块写太多,具体可以自行百度. 推导公式 对于一串是圆和正方形开头和结尾时是十分好 ...

  5. Scrapy 框架结构及工作原理

    1.下图为 Scrapy 框架的组成结构,并从数据流的角度揭示 Scrapy 的工作原理 2.首先.简单了解一下 Scrapy 框架中的各个组件 组       件 描      述 类   型 EN ...

  6. 【快学springboot】6.WebMvcConfigurer配置静态资源和解决跨域

    勘误 有个朋友说:为什么我配置了WebMvcConfigurer,静态资源static依然能访问?! 这里是本人的失误,我在启动类中添加了EnableWebMvc注解(文章里却没有提及,最好的做法是放 ...

  7. Django:使用django自带的登录模块登录后会默认登录到 /accounts/profile 下的问题

    django settings中LOGIN_REDIRECT_URL默认重定向到/accounts/profile下,可通过配置修改

  8. 十六、JSONObject与JSONArray使用-不刷新页面做回写显示

    需要导入:json-lib-2.2.2-.jar包 1.json:就是一个键对应一个值,超级简单的一对一关系.对于json嵌套,只要记住符号“:”前是键,符号后是值大括号成对找. String arr ...

  9. Numpy与List之间的转换

    说明:在做NLP的时候,经常需要查看当前数组数据的维度,也就是data.shape,而List是没有这个属性的,因此需要先将其转换成Numpy,以下为两者户想转换的方法 List转Numpy:nump ...

  10. Centos7 下vmware NAT模式配置网络连接与DNS

    NAT模式配置网络 1.首先查看NAT模式下的网络 从这边可以知道我的vmware下的nat模式的网络是192.168.109.*网段 上图这个网段也可以修改为别的网段 2.NAT模式下的网关 3.配 ...