java1.4开始,提供NIO的API来开发高性能网络服务器,前面介绍的IO方式均为BIO,即阻塞式IO。阻塞式IO在IO操作发起后直到IO操作结果返回这段时间,会一直阻塞该线程,所以基于BIO的网络服务器必须为每个客户端都提供一个独立线程进行处理,否则将会产生很大的延迟。但是当客户端请求并发数量很大时,直接采用BIO方式会产生大量的线程,会导致服务器性能下降。而使用NIO API可以让服务器使用一个或有限几个线程同时处理连接到服务器的客户端。
javaNIO 的API核心由下列几个类组成
一、Selector、SelectableChannel、SelectionKey介绍
Selector对象是SelectableChannel对象的多路复用器,所有的采用非阻塞IO的Channel都应该注册到Selector对象中。
Selector对象创建:调用Selector类的静态方法open(),该方法会将使用系统默认的Selector来返回新的Selector。
Selector有什么用:Selector可以同时监控多个SelectableChannel的IO状况,一个Selector实例有三个SelectionKey集合,下面介绍这三个SelectionKey集合是什么东西。
selector.keys()方法,返回注册在该Selector上的所有Channel,这是所有的SelectionKey集合
selector.selectedKeys()方法,返回所有可通过select()方法获取、需要进行IO处理的Channel,代表被选择的SelectionKey集合
被取消的SelectionKey集合:代表了所有被取消注册关系的Channel,在下一次执行select()方法时,这些Channel对应的SelectionKey会被彻底删除,程序通常无需访问该集合。
Selector提供的与select()方法相关的方法:
int select();监控所有注册的Channel,当他们中间由需要处理的IO操作时,该方法返回,并将对应的SelectionKey加入被选择的SelectionKey集合中,返回这些Channel的数量。可以传入一个long型参数,作为监控的超时时长。
int selectNow():执行一个立即返回的select()操作,相对于无参数的select()方法而言,该方法不会阻塞线程。
Selector wakeup():使一个还未返回的select()方法立即返回。
那么什么是SelectableChannel呢?在NIO API里面,由三种关键的类,分别为Channel、Buffer、Charset,SelectableChannel也是一种Channel,它代表可以支持非阻塞IO的Channel对象,可以被注册到Selector上,这种注册关系由SelectionKey实例表示,这就是这三个类的关系。
这三个类的工作方式:程序调用SelectableChannel的register()方法将其注册到指定Selector对象上,当该Selector上的某些SelectableChannel上有需要处理的IO操作时,程序调用Selector对象的select()方法去获取它们的数量,并可以通过selectedKeys()方法返回他们对应的SelectionKey集合------通过该集合就可以获取所有需要进行IO处理的SelectableChannel集。
下面介绍下SelectableChannel提供的其他方法
SelectableChannel configureBlocking(boolean block):设置是否采用阻塞模式
boolean isBlocking(): 返回该Channel是否是阻塞模式
int validOps():返回一个整数值,表示这个Channel所支持的IO操作(不用的SelectableChannel所支持的IO操作不同,例如ServerSocketChannel代表一个ServerSocke的Channel,它就只支持OP_ACCEPT操作,在SelectionKey中,用静态常量定义了4种IO操作:OP_READ(1)、OP_WRITE(4)、OP_CONNECT(8)、OP_ACCEPT(16),这四个值任意的个数 按位或的结果和相加的结果相等,而且它们任意个数相加结果总不相同,所以可以根据该方法的返回值确定SelectableChannel()支持的操作。)
boolean isRegistered():返回该Channel是否已经注册在一个或多个Selector上。
SelectionKey KeyFor(Selector sel) 返回该Channel和sel Selector之间的注册关系,如果不存在注册关系,则返回null。
上面谈到的SelectableChannel类是一个抽象类,ServerSocketChannel、SocketChannel为该抽象类在网络IO的两个实现类,均支持非阻塞操作,ServerSocketChannel对应于ServerSocket,只支持OP_ACCEPT操作,提供accept()方法;SocketChannel,对英语Socket,支持OP_CONNECT、OP_READ、和OP_WRITE操作。而且这个类还实现了ByteChannel接口、ScatteringByteChannel接口和GatheringByteChannel接口,所以可以直接通过SocketChannel来读写ByteBuffer对象。
so,具体的代码应该怎么写呢?下面给出一般写法:
Selector selector = Selector.open(); //使用ServerSocketChannel监听本机指定端口30000 ServerSocketChannel server = new ServerSocketChannel.open(); InetSocketAddress isa = new InetSocketAddress("127.0.0.1","30000"); server.bind(isa); //设置该Channel以非阻塞方式工作,通常多路复用IO要搭配非阻塞IO使用 server.configureBlocking(false); server.register(selector,SelectionKey.OP_ACCEPT); while (selector.select() > 0){ for(SelectionKey sk : selector.selectedKeys()){ //从selector上的已选择Key集中删除正在处理的SelectionKey selector.selectionKeys().remove(sk); if(sk.isAcceptable()){ //调用accept方法,产生SocketChannel SocketChannel sc = server.accept(); //设置使用非阻塞模式 sc.configureBlocking(false); sc.register(selector,SelectionKey.OP_READ); //将sk对应的Channel设置成准备接收其他请求 sk.interesOps(SelectionKey.OP_ACCEPT); } if(sk.isReadable()){ //。。。 } } }
上面是使用多路复用IO+非阻塞IO的最简单的服务端代码。 客户端的IO方式可以自定义。由于java强势与服务端,所以这里仅仅介绍java服务端代码
二、javaNIO实质
javaNIO看起来好像很抽象难以理解,但是它实际上是对操作系统IO过程的一种近似描述。Selector类是对多路复用IO的抽象,所以想要真正理解NIO就要理解操作系统的五种IO方式,详情见下一节。