JDK中nio实现
ServerSocketChannel的继承关系
-
channel接口
-
InterruptibleChannel接口
- 是可以中断的
-
NetworkChannel接口
- 与网络通信有关
-
AbstractInterruptibleChannel抽象类
- 对InterruptibleChannel接口的实现
-
SelectableChannel抽象类
-
AbstractSelectableChannel抽象类
-
ServerSocketChannel抽象类
SocketChannel的继承关系
- SocketChannel的继承关系右边部分与ServerSocketChannel相同,多出来一部分读写Channel,即从channel又多出来两个分支
- WritableByteChannel接口
- ReadableByteChannel接口
- GatheringByteChannel接口
- ByteChannel接口
- ScatteringByteChannel接口
AbstractInterruptibleChannel—上述两者的第一个实现类
- 可异步、可中断的channel
- 可异步关闭:有一个线程阻塞在当前channel某个操作上,另一个线程进来调用了这个channel的close方法,阻塞的线程就会抛出一个异步关闭的异常
- 可中断:被阻塞的线程,另一个线程执行阻塞线程的interrupt方法,阻塞线程就会抛出线程中断异常
- interrupt方法,只是把标志位有false改为true,并不会真正的中断线程,是否中断线程由当前线程确定
- 调用iterrupt方法的时候,利用Thread.blockedOn方法传递一个方法,来做channel.close这件事,调用blockedOn方法时传入参数是Interruptible,这是一个接口
blockedOn
- Thread线程里面也有这个方法
begin
-
interruptor对象为空,会初始化这个对象,这个初始化方法里面实现了关闭channel,同时指定了需要中断的线程
-
然后执行blockedOn方法
-
Selector中的中断也是采用这种模式
end
- 如果判定当前线程要中断了,抛出两个异常
- ClosedByInterruptException
- AsynchronousCloseException
begin方法要和end方法组合起来使用
-
try{ begin(); }finally{ end(); }
-
为阻塞的线程实现了异步中断和关闭
SelectableChannel
-
在AbstractInterruptibleChannel的基础上,增加了selectorkey到selector上的注册
-
register方法声明和定义
AbstractSelectableChannel
- 实现了selectorKey的注册
register
-
进行各种检查
- channel是否打开
- 是否阻塞,所以在写handler时要把ServerSocketChannel设置成非阻塞,不然会抛出异常
-
findKey
- 轮询keys数组,是否有当前key
- 如果有,则更新这个key的操作,同时传入新的属性参数
- 如果不存在,则进行注册,并添加到keys数组中
其他方法
- 都是围绕selectorKey进行的相关操作
- 数组扩容
- key移除
- …
SocketChannel
- 也是一个抽象类,主要是对底层socket访问的抽象
- bind
- connect
- 非抽象方法
- open
open
-
SelectorProvider.provider().openSocketChannel()
SelectorProviderImpl–SelectorProvider
- 返回socketChannel的实现
SocketChannelImpl
-
定义了socketChannel的生命周期的状态参数值
- 初始化完成变成未连接
- 调用connect方法,调用成功变成已连接,如果是非阻塞式的调用,变成pending_connect(等待连接)
-
connect
-
抛开各种检查,最终底层还是交给socket的原生connect0方法
-
支持可异步中断、可异步关闭的方法,begin–end的组合方法使用
-
ServerSocketChannel
-
抽象方法
- bind
- accept
-
非抽象方法
- open
open
- 与SocketChannel是类似的
ServerSocketChannelImpl
-
与服务器通讯相关
-
bind
-
Net.bind执行绑定方法
-
Net.listen执行监听方法
-
-
accept
- 可异步中断
Selector
-
public abstract class Selector implements Closeable
-
本质是一个抽象类
-
服务器和客户端的通讯是不定时的,虽然支持百万连接,但是大部分都是空闲的,这就是为什么提出io多路复用模型
-
selecor的实现是与操作系统相关的
- windows
- linux
Selector.open
- 跟channel方法一样
SelectorProviderImpl
- openSelector仍然是一个抽象方法
DefaultSelectorProvider
-
缺省实现
-
create
-
如果是sun公司,DevpollSelectorProvider
-
如果是linux,EpollSelectorProvider
EpollSelectorImpl
-
jdk会根据操作系统类型返回该操作系统的provider,再返回对应操作系统的SelectorImpl
SelectorImpl的继承关系
- AutoCloseable接口
- Closeable接口
- Selector抽象类
- AbstractSelector抽象类
SelectorImpl
-
SelectorImpl实例化
-
实例化两个key集合
publicKeys存放所有key
publicSelectedKeys存放已经就绪的key
-
-
register-----channel的注册方法,最终调用链的最后还是由selector实现的
-
@Override protected final SelectionKey register(AbstractSelectableChannel ch, int ops, Object attachment) { if (!(ch instanceof SelChImpl)) throw new IllegalSelectorException(); SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this); if (attachment != null) k.attach(attachment); // register (if needed) before adding to key set implRegister(k); // add to the selector's key set, removing it immediately if the selector // is closed. The key is not in the channel's key set at this point but // it may be observed by a thread iterating over the selector's key set. keys.add(k); try { k.interestOps(ops); } catch (ClosedSelectorException e) { assert ch.keyFor(this) == null; keys.remove(k); k.cancel(); throw e; } return k; }
1.首先包装成SelectionKeyImpl
2.implRegister是一个抽象方法,又需要跳到EPollSelectorImpl的实现去看
-
-
select
- 最终都是调用lockAndDoSelect这个方法
-
lockAndDoSelect
-
private int lockAndDoSelect(Consumer<SelectionKey> action, long timeout) throws IOException { synchronized (this) { ensureOpen(); if (inSelect) throw new IllegalStateException("select in progress"); inSelect = true; try { synchronized (publicSelectedKeys) { return doSelect(action, timeout); } } finally { inSelect = false; } } }
-
又调用doSelect方法,又要进入EPollSelectorImpl中
-
EpollSelectorProvider
- linux下Selector.open返回的
EPollSelectorImpl—直接与操作系统交互
fd0和fd1
-
fd0 fd1
-
管道的读端文件描述符和管道的写端文件描述符
-
操作系统中的管道本质上是一块内存,unix中比较早的版本中就提出来了,主要是用来进程间的通信
-
进程间的通信?
1.读写同一个文件
2.RPC通讯
3.管道,在内存中开辟一块区域,在两个进程间搭起一个桥梁
-
-
为什么要用fd0和fd1?
- 在线程中断的时候,fd1会写入一个字节,fd0会读数据,同时会唤醒线程
-
implRegister方法
- 1.获取一个相关文件描述符,不管是哪个channel
- 2.把它的相关key缓存到fd2Key中
- 3.pollWrapper.add(fd)
- 4.keys.add(ski)
- 但是这一堆注册都是jdk级别的注册,还没有操作系统级别的注册,实际是在调用select方法时才注册到操作系统
-
doSelect方法----做了三件事
- 1.调用原生native方法(pollWrapper.poll—>epoll_wait)去获取已经就绪的文件描述符
- 2.processDeregisterQueue----对取消注册的去做一个实际的取消
- 3.updateSelectedKeys----哪一些key已经就绪,方便代码中去遍历和循环
-
processDeregisterQueue在select方法中为什么要调用两次?
- poll是阻塞的,阻塞的时候有可能有的channel又关闭了
pollWrapper
- 把底层对epoll的使用进行了保证
EPollArrayWrapper
-
对epoll实际操作的一个封装
- pollWrapper.poll,底层真正调用的都是linux底层对epoll的实现
-
EPOLLIN
- epoll.c中进行事件注册时也用到了EPOLLIN
-
EPollArrayWrapper的构造方法
-
EPollArrayWrapper() throws IOException { // creates the epoll file descriptor epfd = epollCreate(); // the epoll_event array passed to epoll_wait int allocationSize = NUM_EPOLLEVENTS * SIZE_EPOLLEVENT; pollArray = new AllocatedNativeObject(allocationSize, true); pollArrayAddress = pollArray.address(); // eventHigh needed when using file descriptors > 64k if (OPEN_MAX > MAX_UPDATE_ARRAY_SIZE) eventsHigh = new HashMap<>(); }
-
epfd = epollCreate();
创建epoll的文件描述符
-
pollArray = new AllocatedNativeObject(allocationSize, true); pollArrayAddress = pollArray.address();
1.维护一些与epoll读写相关的数组,比如说事件
2.pollArray = new AllocatedNativeObject(allocationSize, true);
直接在堆外创建一个对象,放在直接内存之上
-
// events for file descriptors with registration changes pending, indexed // by file descriptor and stored as bytes for efficiency reasons. For // file descriptors higher than MAX_UPDATE_ARRAY_SIZE (unlimited case at // least) then the update is stored in a map. private final byte[] eventsLow = new byte[MAX_UPDATE_ARRAY_SIZE]; private Map<Integer,Byte> eventsHigh;
事件在保存时不要我们那么粗暴,做了调整,当文件描述符比较的小时候放到字节数组中,如果文件描述符比较大或者比较多的时候,就放在一个map中
-
-
也提供了获取文件描述符、事件相关的各种方法
-
poll方法
-
updateRegistrations,更新相关注册信息
这个方法里面会调用epollCtl,进行更新,如果是有的事件就更新,如果是没有的,就新增
-
epollWait—等待有通道就绪,目前有io事件发生了,这个方法有可能会有长时间的阻塞
-
private Map<Integer,SelectionKeyImpl> fdToKey;
- 保存fd到key的映射关系
SelectionKey
- 本质上是channel和selector联系的体现
- 从SelectionKey获取channel
- 从SelectionKey获取selector
- 联系关系的维护
- 检查是否可读
- 检查是否可写
- 检查是否可接受连接
- 检查是否是连接的
- SelectionKey都是int的,相关状态的变更都是通过位操作实现的
AbstractSelectionKey----SelectionKey的抽象类
- cancel方法
- 这个方法并不是马上取消,而是放到一个取消的集合,在适当的时候才执行