【郑州校区】详解Java nio Java nio是在jdk4.0之后引入的,他和我们传统的BIO不同的地方在于,BIO是同步阻塞的,而NIO是同步非阻塞的,在NIO中用到了多路复用技术(这个是借鉴linux的select模式的),通过selector监听多个channel的事件。同时,IO是面向流的,NIO是面向缓冲区的。面向流没有用到缓冲,因此效率上没有NIO高。同时在Java的IO模型中jdk1.7之后的AIO是异步的,但在本章不做介绍,大家有兴趣的可以自己研究下。
首先说一下Channel,通道,NIO中Channel的主要实现有:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel。本片主要讲解SocketChannel、ServerSocketChannel,对应的是TCP(Server和Client),前两者分别对应IO、UDP本章不做讲解,有兴趣的可以自己看看。
Buffer,NIO中的关键Buffer实现有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分别对应基本数据类型: byte, char, double, float, int, long, short。当然NIO中还有MappedByteBuffer, HeapByteBuffer, DirectByteBuffer等。Buffer顾名思义:缓冲区,实际上是一个容器,一个连续数组,buffer中有四个标记位,capacity, position, limit, mark。Buffer的api主要就是操作这几个标记位的值,判断标记位之间是否有数据等。 Selector,Selector运行单线程处理多个Channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用Selector就会很方便。
NIO的强大功能部分来自于Channel的非阻塞特性,套接字的某些操作可能会无限期地阻塞。例如,对accept()方法的调用可能会因为等待一个客户端连接而阻塞;对read()方法的调用可能会因为没有数据可读而阻塞,直到连接的另一端传来新的数据。总的来说,创建/接收连接或读写数据等I/O调用,都可能无限期地阻塞等待,直到底层的网络实现发生了什么。慢速的,有损耗的网络,或仅仅是简单的网络故障都可能导致任意时间的延迟。然而不幸的是,在调用一个方法之前无法知道其是否阻塞。NIO的channel抽象的一个重要特征就是可以通过配置它的阻塞行为,以实现非阻塞式的信道。channel.configureBlocking(false)。
在非阻塞式信道上调用一个方法总是会立即返回。这种调用的返回值指示了所请求的操作完成的程度。例如,在一个非阻塞式ServerSocketChannel上调用accept()方法,如果有连接请求来了,则返回客户端SocketChannel,否则返回null。 下面直接上代码。 客户端: [AppleScript] 纯文本查看 复制代码 public class Client {
@Test
public void startClient() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try {
//打开socketChannel,客户端通道
socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//建立连接
socketChannel.connect(new InetSocketAddress("自己电脑ip",8081));
/**
* socketChannel.finishConnect():建立连接后返回true
*/
if(socketChannel.finishConnect()) {
int i=0;
while(true) {
TimeUnit.SECONDS.sleep(1);
String info = "I'm "+i+++"-th information from client";
//清空缓存区,其实就是设置标志位
buffer.clear();
buffer.put(info.getBytes());
//和clear()方法配合调整buffer数组中 position, limit, mark的值。
buffer.flip();
//判断position和limit之间是否有数据,其实就是buffer中是否有元素
/**
* buffer.clear();
* buffer.put(info.getBytes());
* buffer.flip();
* 只要info中有数据,这时候buffer中就有数据
*/
while(buffer.hasRemaining()){
System.out.println(buffer);
/**
*将数据写入客户端通道,socketChannel,同时清空buffer中数据,其实buffer中还是有数据的,只是此时
*position和limit是同一个位置,导致position和limit之间没有数据,
*所以下次调用buffer.hasRemaining()返回就是false
*/
socketChannel.write(buffer);
}
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
finally {
try {
if(socketChannel!=null) {
socketChannel.close();
}
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
服务端代码:
public class Server {
private static final int BUF_SIZE=1024;
private static final int PORT = 8081;
private static final int TIMEOUT = 3000;
@Test
public void selector() {
Selector selector = null;
ServerSocketChannel ssc = null;
try {
selector = Selector.open();
ssc= ServerSocketChannel.open();
//设置服务端socket监听端口
ssc.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞模式
ssc.configureBlocking(false);
//将通道channel注册到选择器上,
/**
* 第二个参数:
* SelectionKey.OP_CONNECT。连接就绪
* SelectionKey.OP_ACCEPT。接收就绪
* SelectionKey.OP_READ。读就绪
* SelectionKey.OP_WRITE。写就绪
*/
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
/**
* select()阻塞到至少有一个通道在你注册的事件上就绪了。
* select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
* selectNow()不会阻塞,不管什么通道就绪都立刻返回
* (此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。
*/
if(selector.select(TIMEOUT) == 0){
System.out.println("==");
continue;
}
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKey key = iter.next();
if(key.isAcceptable()){
System.out.println("handleAccept");
handleAccept(key);
}
if(key.isReadable()){
System.out.println("handleRead");
handleRead(key);
}
if(key.isWritable() && key.isValid()){
System.out.println("handleWrite");
handleWrite(key);
}
if(key.isConnectable()){
System.out.println("isConnectable = true");
}
iter.remove();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try{
if(selector!=null){
selector.close();
}
if(ssc!=null){
ssc.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
public void handleWrite(SelectionKey key) throws IOException{
ByteBuffer buf = (ByteBuffer)key.attachment();
buf.flip();
SocketChannel sc = (SocketChannel) key.channel();
while(buf.hasRemaining()){
sc.write(buf);
}
buf.compact();
}
public void handleAccept(SelectionKey key) throws IOException{
ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
SocketChannel sc = ssChannel.accept();
sc.configureBlocking(false);
sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUF_SIZE));
}
public void handleRead(SelectionKey key) throws IOException{
SocketChannel sc = (SocketChannel)key.channel();
ByteBuffer buf = (ByteBuffer)key.attachment();
long bytesRead = sc.read(buf);
while(bytesRead>0){
buf.flip();
while(buf.hasRemaining()){
System.out.print((char)buf.get());
}
System.out.println();
buf.clear();
bytesRead = sc.read(buf);
}
if(bytesRead == -1){
sc.close();
}
}
} 本篇讲的是nio的一些基本的原理以及基本的使用介绍,nio api的使用大家要通过查阅开发文档来熟悉。同时建议大家熟悉nio后可以看看netty框架或者mina框架。 传智播客·黑马程序员郑州校区地址 河南省郑州市 高新区长椿路11号大学科技园(西区)东门8号楼三层 联系电话 0371-56061160/61/62 来校路线 地铁一号线梧桐街站A口出
|