说NIO的非阻塞原理之前,我们需要先说一下传统的io。传统的IO是按字节传输的,即每次传输一个字节。为了提高数据传输效率,引进了带缓冲区得输入输出模式,这样每次就可以传输大量的字节数,但是,会导致在读(写)缓冲区没有满的情况下,程序会一直等待,直到满或者关闭流才能读取(写入)。这样导致程序阻塞,降低了程序的执行效率。
如今的NIO的实现并非如此,他的传输是块传输,即每次传输一个块,一个块中可以包含大量的字节数,这样可以有效的提高传输速率而不会导致程序阻塞。如果你懂得设计模式,他的实现原理跟Observer模式类似,即观察者模式。我给你贴一段程序代码,你就能很好的理解。
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.spi.*;
import java.net.*;
import java.util.*;
public class NBTest {
public NBTest()
{
}
public void startServer() throws Exception
{
int channels = 0;
int nKeys = 0;
int currentSelector = 0;
//使用Selector
Selector selector = Selector.open();
//建立Channel 并绑定到9000端口
ServerSocketChannel ssc = ServerSocketChannel.open();
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),9000);
ssc.socket().bind(address);
//使设定non-blocking的方式。
ssc.configureBlocking(false);
//向Selector注册Channel及我们有兴趣的事件
SelectionKey s = ssc.register(selector, SelectionKey.OP_ACCEPT);
printKeyInfo(s);
while(true) //不断的轮询
{
debug("NBTest: Starting select");
//Selector通过select方法通知我们我们感兴趣的事件发生了。
nKeys = selector.select();
//如果有我们注册的事情发生了,它的传回值就会大于0
if(nKeys > 0)
{
debug("NBTest: Number of keys after select operation: " +nKeys);
//Selector传回一组SelectionKeys
//我们从这些key中的channel()方法中取得我们刚刚注册的channel。
Set selectedKeys = selector.selectedKeys();
Iterator i = selectedKeys.iterator();
while(i.hasNext())
{
s = (SelectionKey) i.next();
printKeyInfo(s);
debug("NBTest: Nr Keys in selector: " +selector.keys().size());
//一个key被处理完成后,就都被从就绪关键字(ready keys)列表中除去
i.remove();
if(s.isAcceptable())
{
// 从channel()中取得我们刚刚注册的channel。
Socket socket = ((ServerSocketChannel) s.channel()).accept();
SocketChannel sc = socket.getChannel();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ SelectionKey.OP_WRITE);
System.out.println(++channels);
}
else
{
debug("NBTest: Channel not acceptable");
}
}
}
else
{
debug("NBTest: Select finished without any keys.");
}
}
}
private static void debug(String s)
{
System.out.println(s);
}
private static void printKeyInfo(SelectionKey sk)
{
String s = new String();
s = "Att: " + (sk.attachment() == null ? "no" : "yes");
s += ", Read: " + sk.isReadable();
s += ", Acpt: " + sk.isAcceptable();
s += ", Cnct: " + sk.isConnectable();
s += ", Wrt: " + sk.isWritable();
s += ", Valid: " + sk.isValid();
s += ", Ops: " + sk.interestOps();
debug(s);
}
public static void main (String args[])
{
NBTest nbTest = new NBTest();
try
{
nbTest.startServer();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
这是一段很常见的代码,注释已经写的很清楚,在此再总结一下。
首先,应用nio我们需要使用Selector,这是一个观察者模式的典型实现。我们的每个channel都必须注册到selector中才能被监听到。在sun.nio.ch.DefaultSelectorProvider(Selector的实现)内部有集合来存储我们的channel和对应的监听事件(已经封装为SelectionKey)。此时,我们的selector相当于观察者模式中的主题,一旦select方法被调用,就会遍历所有的观察者(此处是SelectionKey),调用它们统一的方法readyOps()来读取触发的事件,假设此处accept事件被触发,将会返回OP_ACCEPT(整型)。如果调用selector.selectedKeys(),将会以Set形式返回监听事件为OP_ACCEPT的所有SelectionKey,然后通过遍历集合,就可以找到我们的channel。