本文共 4791 字,大约阅读时间需要 15 分钟。
这两天看了下NIO相关的源码,梳理了下NIO的流程。我觉得NIO里面比较核心的三个对象是Selector,SelectableChannel,SelectionKey。
我说下我个人对三者职责的理解:
1、Selector
Selector是NIO里面SelectableChannel的多路复用器。他负责管理多个channel和key。
2、SelectableChannel
是可以被多路复用的channel,常用的实现类是ServerSocketChannel、SocketChannel等, 是用来管理socket连接的通道。
3、SelectionKey
这是一个用来维护Selector和一个SelectableChannel的关系,每个channel注册到selector里的时候都会产生一个selectionkey,会聚合channel和selector,有点类似EventKey。
接下来直接上图,解释下过程:
server端:
1.使用ServerSocketChannel.open()创建ServerSocketChannel对象(简称channel)。 2、使用Selector.open()创建Selector对象(简称selector)。 3、使用channel.regist(selector, SelectionKey)将channel注册给selector。 4、注册的时候需要指定SelectionKey的operate值,是int类型,有四种类型OP_READ、OP_WRITE、 OP_CONNECT、OP_ACCEPT,分别对应的值是1,4,8,16。这样注册的时候会生成一个SelectionKey对象(简称key)。 SelectionKey相当于是个令牌,它把每个channel与唯一的selector进行关联。因此key中会聚合channel和selector 的引用。 5、注册的时候会把生成的key放到selector的key集合中。selector中有三个集合,key,selected-key,cancle-key, select-key和cancle-key是key的子集。 6、轮询执行selector.select()方法,阻塞在这里等待连接或操作 7、客户端socket执行connect() 8、ServerSocketChannel会创建SocketChannel对象来创建socket连接跟客户端进行握手连接。 9、socketChannel创建之后会有机制进行唤醒selector 10、selector的select()方法开始执行 11、执行完select()之后,cancelled-key集合被清空,这个期间被注册产生的key会添加到selected-key集合中 12、这个时候可以拿到selected-key集合中的key,进行遍历处理,根据key的类型,可以进行不同的处理逻辑, 通常accept状态的key需要获得到ServerSocketChannel,然后通过ServerSocketChannel.accept()获取SocketChannel, 然后将SocketChannel再注册回selector里面,不过类型设置为Read或者Write。对于Read和Write类型的key,就可以 通过key.channel()获取SocketChannel对象,然后进行SocketChannel.write或者SocketChannel.read进行发送给端上数据 或读取client端发来的内容。 13、当client端往server端消息时,也会唤醒selector,继续进行10,11,12以上属于自己理解和梳理的,有不对之处可以留言。
下面贴下NIO服务端的示例代码,在示例代码中客户端就是启动个socket就可以,所以客户端用不用nio无所谓。
public class Server { public static void main(String[] args) throws IOException { ServerSocketChannel ssh = ServerSocketChannel.open(); ssh.bind(new InetSocketAddress(8888)); ssh.configureBlocking(false); Selector selector = Selector.open();// Selector selector = SelectorProvider.provider().openSelector(); //这种创建方式也可以 ssh.register(selector, SelectionKey.OP_ACCEPT); System.out.println("server started, listening on "+ssh.getLocalAddress()); int count=0; while(true){ selector.select(); SetselectionKeys = selector.selectedKeys(); Iterator iterator = selectionKeys.iterator(); while(iterator.hasNext()){ SelectionKey key = iterator.next(); handler(key); iterator.remove(); } } } private static void handler(SelectionKey key) { if(key.isAcceptable()){ ServerSocketChannel channel = (ServerSocketChannel)key.channel(); try { SocketChannel accept = channel.accept(); accept.configureBlocking(false); accept.register(key.selector(), SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } }else if(key.isReadable()){ SocketChannel sc = (SocketChannel) key.channel(); ByteBuffer wbb = ByteBuffer.allocate(1024); try { wbb.clear(); sc.read(wbb); System.out.println(new String(wbb.array(),"gbk")); } catch (IOException e) { e.printStackTrace(); } finally { if(sc != null) { try { sc.close(); } catch (IOException e) { e.printStackTrace(); } } } } }}
总结:
Selector是NIO里面SelectableChannel的多路复用器。
Selector通过自己的open()创建,ServerSocketChannel(简称channel)也是通过自己的open()创建。
当channel.regist(selector,SelectionKey)的时候,一个SelectionKey会被创建,并且将channel和selector会聚合到SelectionKey里面,
同时这个key会被添加到selector的keyset集合里面。
当selector.select()的时候,selector里面的cancelled-keySet集合元素会被清除,cancelled-keySet集合会被清空,cancelled-keySet里面的key里的channel会取消注册。
然后这个key会添加到selector的selected-keyset集合里面,同时selector里面的Cancelkeyset也会被清除。
当key调用cancle()或者key的channel被close()的时候,这个key会被放置到selector的cancelkeyset里面。
通过调用set.remove()或者iterator.remove()可以将一个key从selected-keyset里面直接移除,但并不是移动到cancelkey集合里。
selector.select()是阻塞方法,要被打算阻塞的话,有三种方式:
1、调用selector.wakeup()方法
2、调用selector.close()方法
3、当前阻塞的线程调用thread.interrupt() (这种方式会修改线程状态的同时,调用selector.wakeup()方法)
通常来说,一个selector的set集合,在并发多线程的时候,是不安全的。 当set集合的iterator被创建,但是set集合的内容又被修改了的时候会抛出ConcurrentModificationException异常
selector.select()方法使用了synchronized(this),就是锁的selector自身,当有操作进行唤醒的时候,select()方法执行的时候依旧是锁的状态,这个时候其他线程触发selector.wakeup()的操作都不会生效,这时候操作的key都会作为准备放到selected-key中的状态,只有当下一次selector的select()方法被调用的时候,准备状态的key会被添加到selected-key集合中,然后就可以处理selected-key中所有的key。
一句话: 通过selector拿到所有的selected-key,就相当于拿到了所有可处理的channel,根据key的类型进行不同的处理,最终是要处理SocketChannel里面的信息。
转载地址:http://yhcdi.baihongyu.com/