java nio

  1. 获取Selector

Selector selector = Selector.open();

 Selector.open()执行SelectorProvider.provider().openSelector();会先通过单例模式获取SelectorProvider。

如果provider不为空,则直接返回,如果为空,则通过java提供的访问安全控制获取SelectorProvider。

  1. 查看系统配置是否有java.nio.channels.spi.SelectorProvider定义的配置,如果有,则通过反射实例话类并返回
  2. 通过SPI查看是否有扩展的SelectorProvider。如果有返回
  3. 通过DefaultSelectorProvider创建默认的SelectorProvider。

上图为linux的DefaultSelectorProvider,可以看到在linux内核在2.6以上的,使用的是EPollSelectorProvider。

在不同的系统DefaultSelectorProvider实现不一样。Windows返回WindowsSelectorProvider,linux返回的是EPollSelectorProvider。Mac返回的是KQueueSelectorProvider。

  1. 然后调用相应SelectorProvider的openSelector()

以mac为例,实例化了一个KQueueSelectorImpl。KQueueSelectorImpl的继承关系如图

//构造函数

KQueueSelectorImpl(SelectorProvider var1) {

   //调用父类的构造函数,传入的是一个SelectorProvider,在Mac上是KQueueSelectorProvider

    super(var1);

   long var2 = IOUtil.makePipe(false);//native方法

   this.fd0 = (int)(var2 >>> 32);//高32位存放的是Pipe管道的读端的文件描述符

   this.fd1 = (int)var2;//低32位存放的是Pipe管道的写端的文件描述符

   this.kqueueWrapper = new KQueueArrayWrapper();

   this.kqueueWrapper.initInterrupt(this.fd0, this.fd1);

   this.fdMap = new HashMap();

   this.totalChannels = 1;

}

IOUtil.makePipe(false); 是一个static native方法,所以我们没办法查看源码。但是我们可以知道该函数返回了一个非堵塞的管道(pipe),底层是通过Linux的pipe系统调用实现的;创建了一个管道pipe并返回了一个64为的long型整数,该数的高32位存放了该管道读端的文件描述符,低32位存放了该pipe的写端的文件描述符。@link http://www.importnew.com/26258.htmlhttps://blog.youkuaiyun.com/u010853261/article/details/53464475

 

2.ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

ServerSocketChannel.open()执行了SelectorProvider.provider().openServerSocketChannel(),SelectorProvider.provider()和第一步一样,由于第一步已经获取了KQueueSelectorProvider,在直接执行KQueueSelectorProvider.openServerSocketChannel()。由于openServerSocketChannel()属于KQueueSelectorProvider的父类SelectorProviderImpl,返回的是ServerSocketChannelImpl。在各个系统一致。

ServerSocketChannelImpl(SelectorProvider var1) throws IOException {

       super(var1);

       this.fd = Net.serverSocket(true);////获取ServerSocket的文件描述符 

       this.fdVal = IOUtil.fdVal(this.fd);// //获取文件描述的id         

       this.state = 0;

    }

从上面来看,ServerSocketChannelImpl的初始化主要是初始化ServerSocket通道线程thread,地址绑定,接受连接同步锁,默认创建ServerSocketChannelImpl的状态为未初始化,文件描述和文件描述id,如果使用本地地址,则获取本地地址。

3.serverSocketChannel.configureBlocking(false);

此方法调用的是native方法,将通道设置为非阻塞模式

4. serverSocketChannel.socket().bind(new InetSocketAddress(2000));

调用socket()方法,返回ServerSocketAdaptor实例,调用ServerSocketAdaptor的bind(),然后调用ServerSocketChannelImpl的bind().进行端口绑定和监听。

  1. serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);调用AbstractSelectableChannel.register()

首先判断该通道是否打开,以及SelectionKey是否合法是否为非阻塞模式。此方法也可以在通道上附带对象,方便后续使用。然后调用SelectorImpl.register()。调用KQueueSelectorImpl. implRegister()。将通道和selector进行绑定。

  1. 循环调用KQueueSelectorImpl.select()查看是否有第五步注册的SelectionKey.OP_ACCEPT事件。Selector有三个方法进行select。一个是select()阻塞,直到有事件产生。select(timeout)超时阻塞,如果达到超时时间时没有事件则返回。selectNow()无论有没有事件立即返回。这三个方法都调用的SelectorImpl.lockAndDoSelect(),根据传入的超时事件进行区别。lockAndDoSelect是加锁的,线程安全的。如果有进行处理实际的C代码可以参考https://juejin.im/entry/5b51546df265da0f70070b93

 

byteBuffer

java提供了对通道的高速缓存byteBuffer。有三个关键字mark,position,limit,capacity

mark用于对当前position的标记

position表示当前可读写的指针,如果是向ByteBuffer对象中写入一个字节,那么就会向position所指向的地址写入这个字节,如果是从ByteBuffer读出一个字节,那么就会读出position所指向的地址读出这个字节,读写完成后,position加1

limit是可以读写的边界,当position到达limit时,就表示将ByteBuffer中的内容读完,或者将ByteBuffer写满了。

capacity是这个ByteBuffer的容量,上面的程序中调用ByteBuffer.allocate(128)就表示创建了一个容量为capacity字节的ByteBuffer对象。

每次读完后需要调用filp()进行反转后才能写,同样的每次写之前也需要调用filp()进行反转才能读。

 

总结

通过上面我们对源码的分析,javanio也是通过调用各个不同的系统实现不同的方式,然后绑定channelselector,通过循环调用查询响应的事件,然后进行处理。Java nio在各个不同的系统使用的I/O模式都不相同,实际以系统为准,通过第一节对linuxI/O模型进行分析,linux下使用I/O复用模型,能够达到最好的效果。简述java nio的使用,第一步创建一个通道、一个selector,设置为非阻塞并且绑定到响应的端口,然后将通道注册到selector。通过selectorselect()进行循环,查询出感兴趣的事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值