博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java NIO
阅读量:4037 次
发布时间:2019-05-24

本文共 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();            Set
selectionKeys = 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/

你可能感兴趣的文章
VC++ MFC SQL ADO数据库访问技术使用的基本步骤及方法
查看>>
VUE-Vue.js之$refs,父组件访问、修改子组件中 的数据
查看>>
Vue-子组件改变父级组件的信息
查看>>
Python自动化之pytest常用插件
查看>>
Python自动化之pytest框架使用详解
查看>>
【正则表达式】以个人的理解帮助大家认识正则表达式
查看>>
性能调优之iostat命令详解
查看>>
性能调优之iftop命令详解
查看>>
非关系型数据库(nosql)介绍
查看>>
移动端自动化测试-Windows-Android-Appium环境搭建
查看>>
Xpath使用方法
查看>>
移动端自动化测试-Mac-IOS-Appium环境搭建
查看>>
Selenium之前世今生
查看>>
Selenium-WebDriverApi接口详解
查看>>
Selenium-ActionChains Api接口详解
查看>>
Selenium-Switch与SelectApi接口详解
查看>>
Selenium-Css Selector使用方法
查看>>
Linux常用统计命令之wc
查看>>
测试必会之 Linux 三剑客之 sed
查看>>
Socket请求XML客户端程序
查看>>