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

最通俗易懂的NIO讲解

2018-02-23 12:33 561 查看

一、NIO是什么?

NIO的全称是New I/O,与之相对应的是Java中传统的I/O,这里都指的是Java的API包。
传统的IO包提供的是同步阻塞IO,即当用户线程发出IO请求后,内核会去查看数据是否已经就绪,若未就绪,则用户线程会处于阻塞状态(让出CPU),当数据就绪后,内核会将数据复制到用户线程,并把结果返回给用户线程,同时接触用户线程的阻塞,同步体现在用户线程需要等待数据就绪后才能向后执行(后面的执行依赖于前面的结果)。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,线程数量也会受到。
而NIO包提供的IO是同步非阻塞IO,非阻塞体现在用户线程发起IO请求后,会直接得到返回结果,即便在数据未就绪的情况下,也能马上得到失败信息。而同步体现在用户线程需要主动去轮询直到发现数据就绪,再主动将数据从内核拷贝到用户线程。服务器实现模式为多个连接一个线程(IO多路复用),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

传统IO模型
NIO
客户端个数IO线程
1:1
M:1(1个IO线程处理多个客户端连接)
IO类型(阻塞)
阻塞IO
非阻塞IO
IO类型(同步)
同步IO
同步IO(IO多路复用)
API使用难度
简单
非常复杂
调式难度
简单
复杂
可靠性
非常差

吞吐量


二、NIO包API的用法。

(1)Channel:Java NIO中的所有I/O操作都基于Channel对象,一个Channel(通道)代表和某一实体的连接,这个实体可以是文件、网络套接字等。也就是说,通道是Java NIO提供的一座桥梁,用于我们的程序和操作系统底层I/O服务进行交互。
FileChannel:文件通道,从输入流输出流中获取实例,常见的使用场景就是从一个文件拷贝其内容到另一个文件。FileInputStream fis = new FileInputStream("E://zfb.txt");
FileChannel ifc = fis.getChannel();
FileOutputStream os = new FileOutputStream("E://zfb2.txt");
FileChannel ofc = os.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (ifc.read(buffer) != -1){
System.out.println(1);
buffer.flip();
ofc.write(buffer);
buffer.clear();
}SocketChannel/ServerSocketChannel:套接字通道,通过静态方法获取实例,使用场景在最后会给出demo。SocketChannel socketChannel = SocketChannel.open();(2)Buffer:NIO中所使用的缓冲区不是一个简单的byte数组,而是封装过的Buffer类,通过它提供的API,我们可以灵活的操纵数据。与Java基本类型相对应,NIO提供了多种 Buffer 类型,如ByteBuffer、CharBuffer、IntBuffer等,区别就是读写缓冲区时的单位长度不一样(以对应类型的变量为单位进行读写)。Buffer中有3个很重要的变量,它们是理解Buffer工作机制的关键,分别是capacity (总容量)、position (指针当前位置)、limit (读/写边界位置)。
接下来看三个方法flip、rewind、clearpublic final Buffer flip() {//用于写模式到读模式的转换
limit = position;//设置上届为当前位置
position = 0;//当前位置设置为0
mark = -1;//重置写标记
return this;
}
public final Buffer rewind() {//设置当前位置为0
position = 0;
mark = -1;
return this;
}
public final Buffer clear() {//通过指针移动的方式清空缓冲区
position = 0;//设置当前位置为0
limit = capacity;//上限为容量
mark = -1;
return this;
}

(3)Selector:Selector(选择器)是一个特殊的组件,用于采集各个通道的状态(或者说事件)。我们先将通道注册到选择器,并设置关心的事件,然后就可以通过调用select()方法,监听关注事件的发生。通道有4个事件可供我们监听:Accept、Connect、Read、Write。
selector.select()方法是阻塞的方法!!!若注册的通道没有事件到达,则不会向下执行。但是NIO的IO机制是非阻塞的!!!

三、NIO的编程模型——基于Reactor模式的事件驱动。

NIO是同步非阻塞的,但是提供了基于Selector的异步网络IO。这句话的意思是NIO的IO机制是同步非阻塞的,而基于Selector这个组件的编程模型(Reactor)是事件驱动的,是异步的。
下面来看一下一般采用NIO的编程模型:



demo服务端代码:public class NIOServer {

private final static int BUFFER_SIZE = 1024;

private final static int PORT = 52621;

private ServerSocketChannel serverSocketChannel;

private ByteBuffer byteBuffer;

private Selector selector;

public NIOServer(){
try {
init();
} catch (IOException e) {
e.printStackTrace();
}
}

public void init() throws IOException {//初始化
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(PORT));
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);

}

private void listen() throws IOException {
while (true){
if (selector.select() == 0){
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = ssc.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
replyClient(socketChannel);
}
if (key.isReadable()){
readDataFromClient(key);
}
iterator.remove();
}
}
}

private void readDataFromClient(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
byteBuffer.clear();
int n;
while ((n = socketChannel.read(byteBuffer)) > 0){
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
socketChannel.write(byteBuffer);
}
byteBuffer.clear();
}
if (n < 0){
socketChannel.close();
}
}

private void replyClient(SocketChannel channel) throws IOException {
byteBuffer.clear();
byteBuffer.put("Hello,I am server!".getBytes());
byteBuffer.flip();
channel.write(byteBuffer);
}

public static void main(String[] args) {
try {
new NIOServer().listen();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码:public class NIOClient {

private final static int BUFFER_SIZE = 1024;

private final static int PORT = 52621;

private SocketChannel socketChannel;

private ByteBuffer byteBuffer;

public void connect() throws IOException {
socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", PORT));
byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
receive();
}

private void receive() throws IOException {
while (true){
int n;
byteBuffer.clear();
while ((n = socketChannel.read(byteBuffer)) > 0){
byteBuffer.flip();
System.out.println("从服务器收到消息: " + new String(byteBuffer.array()));
//socketChannel.write(byteBuffer);//发消息
byteBuffer.clear();
}
}
}

public static void main(String[] args) {
try {
new NIOClient().connect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息