NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(多路复用器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。
IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
NIO优点:
- 通过Channel注册到Selector上的状态来实现一种客户端与服务端的通信。
- Channel中数据的读取是通过Buffer , 一种非阻塞的读取方式。
- Selector 多路复用器 单线程模型, 线程的资源开销相对比较小。
服务端代码:NIOServer.java
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws Exception{
//创建一个serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//创建Selector
Selector selector = Selector.open();
//绑定端口6666,在服务端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置非阻塞模式
serverSocketChannel.configureBlocking(false);
//将channel注册到selector中,关心事件为op_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环监听客户端连接
while (true){
//selector等待1s钟,如果没有事件发生则可以去做别的事情
if(selector.select(1000)==0){
System.out.println("服务端等待了1s,没有事件发生");
continue;
}
//如果>0,则得到selectionKeys集合,已经获取到关注的事件了,selectionKeys是关注事件的集合
//通过selectionKeys反向获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍历selectionKeys集合,使用迭代器
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
//根据key对应的通道发生的事件做相应的处理
//相当于有客户端连接,给该客户端生成一个socketchannel
if(key.isAcceptable()){
SocketChannel socketChannel = serverSocketChannel.accept();
//设置为非阻塞
socketChannel.configureBlocking(false);
System.out.println("客户端连接成功,生成了一个socketchannel"+socketChannel.hashCode());
//将sockerchannel注册到selector,关注事件为read,同行关联一个buffer
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){
//通过key反向获取socketChannel
SocketChannel channel = (SocketChannel)key.channel();
//获取到该channel关联的buffer
ByteBuffer buffer=(ByteBuffer)key.attachment();
channel.read(buffer);
System.out.println("客户端"+new String(buffer.array()));
}
//手动从集合中移除当前的selectionkey,防止重复操作
iterator.remove();
}
}
}
}
两个客户端的代码
NIOClient.java
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws Exception{
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//提供服务器端的IP和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if(!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("因为连接需要时间,客户端没有阻塞,可以做其他工作。。");
}
}
//如果连接成功,发送数据
String str="客户端ZTY已连接";
//直接将字符串对应的字节数组包裹到buffer中,不用指定大小
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将buffer写入到channel
socketChannel.write(buffer);
System.in.read();
}
}
NIOClient2.java
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient2 {
public static void main(String[] args) throws Exception{
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//提供服务器端的IP和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if(!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("因为连接需要时间,客户端没有阻塞,可以做其他工作。。");
}
}
//如果连接成功,发送数据
String str="客户端LPJ已连接";
//直接将字符串对应的字节数组包裹到buffer中,不用指定大小
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将buffer写入到channel
socketChannel.write(buffer);
System.in.read();
}
}
先运行服务端。截图如下:
再运行客户端 1(NIOClient.java)后,服务端界面产生输出:
运行客户端 2(NIOClient2.java)后,服务端界面产生输出:
搞完NIO,就离掌握Netty不远了,因为Netty是再NIO基础上的。