您的位置:首页 > 编程语言 > Java开发

java NIO

2006-03-20 20:56 337 查看
传统的网络运行的性能瓶颈通常在I/O读写,包括对端口和文件的操作。过去,当打开一个Socket的I/O通道后,使用下列语句:

Socket socket = new Socket(url.port);
InputStream in = socket.getInputStream();
While(!Thread.interrupted()){
Int byteRead = in.read();

}
其中,read()守候在端口边不断读取传输过来的字节内容,如果读取不到任何字节内容,read()也只能等待,这会使得整个系统被锁定,影响程序系统继续做其它事情。一个普遍的改进办法就是开设线程,让线程去等待或轮询。但是,这种做法相当消耗资源。更主要的是,当线程轮训后,若有事件发生,只有等到线程下次轮训时才会知道。即没有一种这样的途径:当有事件发生时,能够主动发出通知。
非阻塞I/O作为Reactor模式的实现,实际上提供了一种事件发生、自我激活、主动通知的机制。因此使用非阻塞I/O将大大提高系统的I/O处理能力。
非阻塞I/O中有3个重要的类:Selector、SelectionKey和Channel。
Selector实际上是一个Reactor类,主要监视管理一系列SelectionKey,每个SelectionKey代表一种Channel和Selector之间的关系。在某个Channel上如果发生某种连接事件,Selector将会自动激活产生一个SelectionKey对象。即SelectionKey属于事件对象(Event Object),是动态的,每发生一个连接事件就产生一个SelectionKey对象。
从被激活的SelectionKey中,外界可以知道每个Channel发生的具体事件类型。这些事件包括:是否发生连接、是否可以读或者是否可以写等。
从以上分析可以看出,Selector有自我激活的能力。使用Selector时只要告诉它需要关注的特定事件,Selector将会一直监视这种特定事件,一旦发生,就发出通知。类似火警报警器,一旦发生失火事件,立即会主动激活报警。
由于Selector只负责事件发生,不负责事件处理,事件处理是由开发者编制程序实现,因此,使用者需要自己建立一套获取发生事件的机制。
总之,非阻塞I/O的使用包括两大部分:注册事件和获取事件。
首先,需要向Selector注册外界感兴趣的事件,创建Selector对象如下:
Selector selector = Selector.open();

Selector selector = SelectorProvider.provider().openSelecotr();

Selector是一个观察者,那么它观察谁?就是连接通道Channel,这种Channel是一种SelectableChannel,即可以和Selector发生联系的Channel。SelectableChannel常用的有两种:SocketChannel和ServerSocketChannel,这两种SelectableChannel的区别是可注册的事件不一样。


ServerSocketChannel对应事件:OP_ACCEPT


SocketChannel对应事件:OP_CONNECT、OP_READ、OP_WRITE。

前者一般使用在服务器端,可以从中知道有无可以接收的客户端连接。创建ServerSocketChannel如下:
ServerSocketChannel sc = ServerSocketChannel.open();
创建一个ServerSocketChannel后,需要将其和主机端口进行绑定,例如和192.168.0.1的8009端口绑定:
InetSocketAddress address = new InetSocketAddress(“192.168.0.1”),8009;
Sc.socket().bind(address);
Sc.configureBlocking(false);

现在如果需要从这个ServerSocketChannel了解有无可接受的客户端连接,语法如下:
SelectionKey acceptKey = sc.register(selector,SelectionKey.OP_ACCEPT);
上面一条语句是用Selector注册ServerSocketChannel实例,返回一个Key实例,通常SelectionKey对象都是线程安全型的,但是修改感兴趣的事件操作时,这个方法是被标记为同步的,即在调用interestOps()方法时会锁定一段时间,因此,实际应用中,如果有一个以上的线程来调用同一个Selector对象时,需要使用Selector.wakeup()来解锁。
以上非阻塞I/O的注册事件工作已经准备就绪,那么,在正常运行中,如果Selector发现了事先注册的事件,如何传递出来呢?这就需要建立一个事件获取通道。
其实,获取事件时,只要执行语句selector.select(),这将触发系统内部自动检查所有使用这个Selector注册的Channel状态。如果在某个Channel发现有感兴趣的事件发生了。那么又如何知道是哪个具体Channel发生的呢?
使用selector.selectedKeys()获取一个SelectionKey结果集,遍历这个结果集,通过每个SelectionKey就可以找到发生事件的Channel,这样可以从这个Channel进行读写数据,如下图所示:

这部分结构主要实现了Reactor模式中的事件到达部分,当有读或写等任何实现注册的事件发生时,可以从Selector中获得相应的SelectionKey,从SelectionKey可以找到相应的Channel,从而能获得客户端发送过来的数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: