NIO与Socket笔记 :Selector 的使用

选择器与 I/O 多路复用

Selector选择器是 NIO 技术中的核心组件,可以将通道注册进选择器中,

其主要作用就 是使用 1个线程来对多个通道中的已就绪通道进行选择,然后就可以对选择的通道进行数据 处理,属于一对多的关系,

也就是使用1个线程来操作多个通道,这种机制在 NIO 技术中 称为“ I/O 多路复用” 。

它的优势是可以节省 CPU 资源,因为只有 1 个线程, CPU 不需要在 不同的线程间进行上下文切换 。

线程的上下文切换是一个非常耗时的动作,减少切换对设计 高性能服务器具有很重要的意义 。

 

NIO与Socket笔记 :Selector 的使用

 

从在图 5-1 中可以发现,如果不使用 1/0 多路复用, 则 需要创建多个线程对象,每个线 程都对应一个通道,在对应的通道中进行数据的处理 。 但是,如果在高并发环境下,就会 创建很多的线程对象,造成内存占用率升高,增加 CPU 在多个线程之间上下文切换的时间, 因此,此种设计就不适用于高并发的场景 。

从图 5-2 中可以 发现,使用了 I/O 多路复用后,只需要使用 1 个线程就可 以操作多个通 道,属于一对多的关系 。 它和“不使用 I/O 多路复用”相比最大的优势就是内存占用率下降 了,因为线程对象的数量大幅减少,还有 CPU 不需要过多的上下文切换,这对高并发高频 段处理的业务环境有非常重要的优势 。

线程数会随着通道的多少而动态地增减以进行适配,在内部其实并不永远是一个线程,多 路复用的核心目的就是使用最少的线程去操作更多的通道 。

在 JDK 的源代码中,创建线程的 个数是根据通道的数量来决定的,每注册 1023 个通道就创建 l 个新的线程,这些线程执行 Windows 中的 selectQ 方法来监测系统 socket 的事件,如果发生事件则通知应用层 中的 main 线程 终止阻塞,继续向下运行,处理事件。 可以在CMD中使用jps和jstack来查看创建线程的数量。

学习 I/O 多路复用时一定要明白一个知识点,就是在使用 I/O 多路复用时,这个线 程不是以 for循环的方式来判断每个通道是否有数据要进行处理,而是以操作系统底 层作为“通知器”,来“通知 JVM 中的线程”哪个通道中的数据需要进行处理,这点一定要注意 。 当不使用 for 循环的方式来进行判断,而是使用通知的方式时,这就 大大提高了程序运行的效率,不会出现无限期的 for 循环迭代空运行了

 

核心类 Selector、SelectionKey和 SelectableChannel 的关系

在使用选择器技术时,主要由 3 个对象以合作的方式来实现线程选择某个通道进行业务处理,

这 3 个对象 分别是 Selector、 SelectionKey 和 SelectableChannel。

 

Selector类是抽象类,它是 SelectableChannel对象的 多路复用器。

只有 SelectableChannel通道对象才能被 Selector.java选择器所复用.

因为只有 SelectableChannel类才具有 register(Selector sel, int ops)方法,该方法的作用是将当前的 SelectableChannel通道注册到指定的选择器中,参 数 sel 也说明了这个问题 。

NIO与Socket笔记 :Selector 的使用

由选择器来决定对哪个通道中的数据进行处理,这些能被选择器所处理的通道的父类 就是 SelectableChannel,它是抽象类,

NIO与Socket笔记 :Selector 的使用

 

SelectableChannel类和 FileChannel类是平级关系,都继承自父类 AbstractlntenuptibleChannel。

NIO与Socket笔记 :Selector 的使用

 

NIO与Socket笔记 :Selector 的使用

NIO与Socket笔记 :Selector 的使用

 

SelectableChannel类的子类都可以使用 register (Selector sel, int ops)方法将自身注册到选择器中 。

 

SelectionKey 类的作用 是一 个标识,这个标 识代表 SelectableChannel类已经向 Selector类注 册了 。

NIO与Socket笔记 :Selector 的使用

 

通道类 AbstractlnterruptibleChannel与接口 InterruptibleChannel的介绍

AbstractlnterruptibleChannel类实现了 lnterruptibleChannel接口,该接口的主要作用, 是使通道能 以异步的方式进行关闭与中断 。

 

如果通道实现了 asynchronously 和 closeable 特性,那么,当一个线程在一个能被中断 的通道上出现了阻塞状态,

其他线程调用这个通道的 close()方法时,这个呈阻塞状态的线 程将接收到 AsynchronousC!oseException异常。

 

如果通道实现了 asynchronously 和 closeable,并且还 实 现了 interruptible 特性,

那么,当一个线程在一个能被中断的通道上出现了阻塞状态,其他线程调用这个阻塞线程的 interrupt()方法时,

通道将被关闭,这个阻塞的线程将接收到 ClosedByInterruptException异 常,这个阻塞线程的状态一直是中断状态 。

 

通道类 SelectableChannel 的介绍

AbstractlnterruptibleChannel类的子类就包含抽象类 SelectableChannel和 FileChannel。

NIO与Socket笔记 :Selector 的使用

一个通道至多只能在任意特定选择器上注册一次。

可以通过调用 isRegistered()方法来确定是否已经向一个或多个选择器注册了某个通道 。

 

SelectableChannel 在多线程并发环境下是安全的 。

SelectableChannel要么处于 阻塞模式,要么处于非阻塞模式 。

在阻塞模式中,每一个I/O 操作完成之前都会阻塞在其通道上调用的其他I/O  操作 。

在非阻塞模式中,永远不会阻 塞 I/O操作,并且传输的字节可能少于请求的数量,或者可能根本不传输字节。

可通过调用 SelectableChannel 的 isBlocking()方法来确定其是否为阻塞模式 。

新创建 的 SelectableChannel 总是处于阻塞模式 。

在结合使用基于选择器的 多路复用时 , 非阻塞模式是最有用的 。

向选择器注册某个通道前 , 必须将该通道置于非阻塞模式, 并且在 注销之前可能无法返回到阻塞模式

 

通道类 AbstractSelectableChannel 的介绍

抽象类 AbstractSelectableChannel是可选择通道的基本实现类。

此类定义了处理通道注册、 注销 和关闭机制的各种方法。

它会维持此通道的 当前阻 塞模式及其当前的选择键集 SelectionKey。

它执行 实现 SelectableChannel 规范所需 的所有 同步 。

此类 中所定义的抽象保护方法的实现不必与同一操作中 使用的其他线程同步。

通道类 Server如 cketChannel 与接口 NetworkChannel 的介绍

NIO与Socket笔记 :Selector 的使用

ServerSocketChannel 类是针对面 向流的侦昕套接字 的可选择通道 。

不是侦听网络套接字的完整抽象,必须通过调用 socket()方法所获得的关联 ServerSocket对 象来完成对套接宇选项的绑定和操作 。

不可能为任意的已有 ServerSocket 创建通道,也不可 能指定与 ServerSocketChannel关联的 ServerSocket所使用的 Socketlmpl对象。

 

通过调用此类的 open()方法创建 ServerSocketChannel。

新创建的 ServerSocketChannel 己打开,但尚 未绑定 。

 

多个并发线程可安全地使用服务器套接字通道 ServerSocketChannel。

NIO与Socket笔记 :Selector 的使用

 

 

ServerSocketChannel 类、 Selector 和 SelectionKey 的使用

 

获得 ServerSocketChannel 与 ServerSocket socket 对象

NIO与Socket笔记 :Selector 的使用

 

执行绑定操作

serverSocket .bind( new InetSocketAddress( "localhost ” , 8888));

 

执行绑定操作与设置 backlog

public abstract ServerSocketChannel bind(SocketAddress local, int backlog)方法的作用

是将通道的套接字绑定到本地地址并侦昕连接,通过使用参数 backlog来限制客户端连接的 数 量。

 

服务端允许接受的客户端连接个数上限为 50。

NIO与Socket笔记 :Selector 的使用

 

阻塞与非阻塞以及 accept()方法的使用效果

public abstract SocketChannel accept()方法的作用是接受此通道套接字的连接。

如果此 通道处于非阻塞模式,那么在不存在挂起的连接时,此方法将直接返回 null。 否则,在新的 连接可用或者发生I/O错误之前会无限期地阻塞它。 无论此通道的阻塞模式如何,此方法返回的套接字通道(如果有)将处于阻塞模式 。

 

如何切换 ServerSocketChannel通道的阻塞与非阻塞的执行模式呢?

调用 ServerSocket­ Channel 的 public final SelectableChannel configureBlocking(boolean block)方法即可 。

public final SelectableChannel configureBlocking(boolean block)方法的作用是调整此通道的阻塞模 式,

传入 true是阻塞模式 ,传入 false是非阻塞模式。

NIO与Socket笔记 :Selector 的使用

获得 Selector 对象

 

NIO与Socket笔记 :Selector 的使用

执行注册操作与获得 SelectionKey 对象

SelectableChannel类的 publicfinal SelectionKey register(Selector sel, int ops)方法的作用 是向给定的选择器注册此通道,

返回一个选择键 (SelectionKey)。

 

参数 sel代表要向其注册此通道的选择器,参数 ops代表 register()方法的返回值 Selection­ Key 的可用操作 集 ,操作 集 是在 SelectionKey 类中以常 量 的形式进行提供的,如图 5-26 所示。

 

方法 public final SelectionK.ey register(Selectorsel, int ops)的 ops参数就是通道感兴趣的事件,也就是通道能执行操作的集合,可以对 ops 执行位运 算 。

 

判断注册的状态

SelectableChannel类的 publicfinal boolean isRegistered()方法的作用是判断此通道当前是否 已向任何选择器进行了注册 。

新创建的通道总是未注册的 。

由于对 SelectionKey 执行取消操作 和通道进行注销之间有延迟,因此在已取消某个通道的所有 SelectionKey后,该通道可能在一 定时间内还会保持已注册状态。

关闭通道后,该通道可能在一定时间内还会保持已注册状态。

 

将通道设置成非阻塞模式再注册到选择器

在将通道注册到选择器之前,必须将通道设置成非阻塞模式 

 

使用 configureBlocking (false)方法解决异常

public final SelectableChannel configureBlocking(boolean block)方法的作用是调整此通 道的阻塞模式。如果向一个或多个选择器注册了此通道,则尝试将此通道置于阻塞模式将导 致抛出 IllegalBlockingModeException。 可在任意时间调用此方法 。新 的阻塞模式仅影响在 此方法返回后发起 的 1/0 操作 。 对于某些实现,这可能需要阻塞,直到所有挂起的 1/0 操作 已完成 。 如果调用此方法的同时正在进行另一个此方法或 register()方法的调用,则在另一 个操作完成前将首先阻塞该调用 。

public final boolean isBlocking()方法的作用是判断此通道上的每个 1/0操作在完成前是 否被阻塞 。 新创建的通道总是处于阻塞模式。如果此通道已关闭, 则此方法返回的值是未指 定的。

 

判断打开的状态

public final boolean isOpen()方法的作用是判断此通道是否处于打开状态。

 

获得阻塞锁对象

public final Object blockingLock()方法的作用是获取其 configureBlocki吨()和 register() 方法实现同步的对象,防止重复注册 。

NIO与Socket笔记 :Selector 的使用

 

获得支持的 SocketOption 列表

Set< SocketOption<?>> supportedOptions()方法的作用是返回通道支持的 Socket Option。

 

获得与设置 SocketOption

public abstract <T> ServerSocketChannel setOption(SocketOption<T> name, T value)方法 的作用是设置 SocketOption值。

< T> T getOption(SocketOption<T> name)方法的作用是获取 Socket Option值 。

 

获得 SocketAddress 对象

public abstract SocketA ddress getLocalAddress()方法的作用是获取绑定的 SocketAddress 对象。

 

阻塞模式的判断

 

public final boolean isBlocking()方法的作用是判断此通道上的每个 1/0操作在完成前是 否被阻塞 。

新创建的通道总是处于阻塞模式 。

如果此通道已 关 闭,则此方法返回的值是未指 定的 。 返回值代表当且仅当此通道处于阻塞模式时才返回 true。

 

根据 Selector找到对应的 SelectionKey

public final SelectionKey keyFor(Selector sel)方法的作用是获取通道向给定选择器注册 的 SelectionKey。

同一个 SelectableChannel通道可以注册到不同的选择器对象,然后返回新创建的 Selection­ Key对象, 可以使用 public final SelectionKey keyFor(Selector sel)方法来取得当前通道注册 在指定选择器上的 SelectionKey对象。

 

获得 SelectorProvider 对象

public final SelectorProvider provider() 方法 的作 用是返回创建此通道的 SelectorProvider。

SelectorProvider类的作用是用于选择器和可选择通道的服务提供者类。

通道注册与选择器

1. 相同的通道可以注册到不同的选择器,返回的 SelectionKey 不是同一个对象

 

NIO与Socket笔记 :Selector 的使用

2. 不同的通道注册到相同的选择器,返回的 SelectionKey 不是同一个对象

NIO与Socket笔记 :Selector 的使用

 

3. 不同的通道注册到不同的选择器,返回的 Selectionkey 不是同一个对象

4. 相同的通道重复注册相同的选择器,返回的 SelectionKey 是同一个对象

NIO与Socket笔记 :Selector 的使用

 

返回此通道所支持的操作

public final intvalidOps()方法的作用是返回一个操作集,标识此通道所支持的操作。

NIO与Socket笔记 :Selector 的使用

NIO与Socket笔记 :Selector 的使用

 

执行 Connect 连接操作

 

public abstract boolean connect(SocketAddress remote)方法的作用是连接到远程通道的 Socket。

如果此通道处于非阻塞模式, 则此方法的调用将启动非阻塞连接操作 。

 

如果通道呈阻塞模式, 则立即发起连接;

如果呈非阻塞模式, 则不是立即发起连接,而 是在随后的某个时间才发起连接 。

如果连接是立即建立的,说明通道是阻塞模式,当连接成功时, 则此方法返回 true,连 接失败出现异常。

如果此通道处于阻塞模式, 则此方法的调用将会阻塞,直到建立连接或发生 I/O错误。

 

如果连接不是立即建立的,说明通道是非阻塞模式, 则此方法返回false,并且以后必

须通过调用fishConnect()方法来验证连接是否完成 。

 

判断此通道上是否正在进行连接操作

public abstract boolean isConnectionPending()方法的作用是判断此通道上是否正在进行连 接操作。

返回值是 true代表当且仅当已在此通道上发起连接操作,但是尚未通过调用 finish­ Connect()方法完成连接。

还可以是在通道 accept()之后和通道 close()之前, isConnection­ Pending()方法的返回值都是 true。

 

完成套接字通道的连接过程

public abstract boolean finishConnect()方法的作用是完成套接字通道的连接过程 。

通过 将套接字通道置于非阻塞模式,然后调用其 connect()方法来发起非阻塞连接操作 。

如果连 接操作失败,则调用此方法将导致抛出 IOException。

 

一旦建立了连接,或者尝试已失败,该套接字通道就变为可连接的,并且可调用此方法完成连接序列 。

 

如果已连接了此通道,则不阻 塞 此方法并且立即返回 true。

如果此通道处于非阻塞模式,那么当连接过程尚未完成时,此方法将返回 false。

 

如果此通道处于阻塞模式,当连接成功之后返回 true,连接失败时抛出描述该失败的 、经过检查的异常。

在连接完成或失败之前都将阻塞此方法

 

虽然可在任意时间调用此方法 但如果正在调用此 方法 时在此通道上调用 读取或写入操作,则在此调用完成前将首先阻塞该操作 。

 

类 FileChannel 中的 long transferTo (position, count, WritableByteChannel)方法

 

方法 transferTo()的作用是试图读取此通道文件中给定 position处开始的 count个 字节,并将其写入目标通道中,但是此方法的调用不一定传输所有请求的字节 ,是否传 输取决于通道的性质和状态。

 

方法 publicstatic SocketChannel open (SocketAdd陪ss remote)与 Socket­Option 的执行顺序

 

如果先调用 publicstatic SocketChannel open(SocketAddress remote)方法,然后设置 Sock时,Option, 则不会出现预期的效果,因为在publicstatic SocketChannel open(SocketAddressremote)方法中已 经自 动执行了 connect()方法

 

传输大文件

NIO与Socket笔记 :Selector 的使用

NIO与Socket笔记 :Selector 的使用

NIO与Socket笔记 :Selector 的使用

NIO与Socket笔记 :Selector 的使用

 

验证read 和 write 万法是非阻塞的

执行代码 configureBlocking(false)代表当前的 1/0为非阻塞的,

NIO就是同步非阻塞模 型,所以 read和 write方法也呈现此特性

NIO与Socket笔记 :Selector 的使用

NIO与Socket笔记 :Selector 的使用

 

NIO与Socket笔记 :Selector 的使用