java BIO、NIO学习

在IO执行有两个阶段很重要:html

  • 等待数据准备
  • 将数据从内核复制到进程中

BIO(Blocking I/O)阻塞I/O模型




当用户进程调用了recvfrom这个系统调用,内核就开始了io的第一个阶段:等待数据准备。若是数据还没准备好(好比尚未收到一个完整的udp包),这时候内核要等待足够的数据到来,而在用户进程这边,进程会被阻塞。当内核等到数据准备好,进程将数据从内核中拷贝到用户空间,而后内核返回结果,用户进程才解除block状态,从新运行起来。数组

BIO在IO执行的两个阶段都被阻塞了。bash

示例代码:socket

public static void server(){
        ServerSocket serverSocket = null;
        InputStream in = null;
        try
        {
            serverSocket = new ServerSocket(8080);
            int recvMsgSize = 0;
            byte[] recvBuf = new byte[1024];
            while(true){
                Socket clntSocket = serverSocket.accept();
                SocketAddress clientAddress = clntSocket.getRemoteSocketAddress();
                System.out.println("Handling client at "+clientAddress);
                in = clntSocket.getInputStream();
                while((recvMsgSize=in.read(recvBuf))!=-1){
                    byte[] temp = new byte[recvMsgSize];
                    System.arraycopy(recvBuf, 0, temp, 0, recvMsgSize);
                    System.out.println(new String(temp));
                }
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally{
            try{
                if(serverSocket!=null){
                    serverSocket.close();
                }
                if(in!=null){
                    in.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
复制代码

NIO(Non-Blocking I/O)非阻塞I/O模型

NIO是一种同步非阻塞的IO模型。同步指线程不断轮询IO事件是否就绪,非阻塞是指线程在等待IO的时候,能够作其余任务。同步的核心是Selector,Selector代替了线程自己轮询IO事件,避免了阻塞同时减小了没必要要的线程消耗;非阻塞的核心是通道和缓冲区,当IO事件就绪时,能够经过写入缓冲区,保证IO的成功,无需线程阻塞式地等待。学习


当用户进程调用recvfrom时,系统不会阻塞用户进程,若是数据还没准备好,系统马上返回一个ewouldblock错误给进程,用户进程知道数据还没准备好,用户进程就能够去作其余事了。进程轮询内核查看数据是否准备好。直到数据准备好了,而且收到用户进程的system call,进程复制数据报,而后返回。ui

NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(多路复用器)



Channel

基本上,全部的IO在NIO中都从一个Channel开始。数据能够从Channel读到Buffer中,也能够从Buffer写到Channel。spa

Buffer

Buffer经过如下几个变量来保存这个数据的当前位置状态:线程

capacity:缓冲区数组的总长度
position:下一个要操做的数据元素的位置
limit:缓冲区数组中不可操做的下一个元素的位置
mark:用于记录当前position的前一个位置或者默认是-1

0 <= mark <= position <= limit <= capacity复制代码


开始时Buffer的position为0,limit为capacity,程序写入数据到缓冲区,position日后移。当Buffer写入数据结束以后,调用flip()方法以后(此时limit=position,position=0),Buffer为输出数据作好准备;当Buffer输出数据结束以后,调用clear()方法(此时limit=capacity,position=0),clear()方法不是清空Buffer的数据,它仅仅将position置为0,将limit置为capacity,为再次向Buffer装入数据作准备;3d

Buffer的使用:code

  • 分配空间:ByteBuffer buf = ByteBuffer.allocate(1024);
  • 写入数据到Buffer:int bytesRead = fileChannel.read(buf);
  • 调用flip()方法:buf.flip();
  • 从Buffer中读取数据:buf.get();
  • 调用clear()方法或compact()方法

Buffer一些重要方法:

  • flip():把limit设置为position,position设置为0,为输出数据作好准备
  • clear():把position设置为0,limit设置为capacity,为再次向Buffer装入数据作准备
  • compact():将全部未读的数据拷贝到Buffer起始处,把position设置到最后一个未读元素的正后面,limit设置为capacity
  • mark():能够标记Buffer中的一个特定的position,以后能够经过reset()恢复到position的位置
  • rewind():将position设回为0,limit保持不变,这样能够重读Buffer中的全部数据。

示例代码:

public static void client(){
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        SocketChannel socketChannel = null;
        try
        {
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("10.10.195.115",8080));
            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());
                    buffer.flip();
                    while(buffer.hasRemaining()){
                        System.out.println(buffer);
                        socketChannel.write(buffer);
                    }
                }
            }
        }
        catch (IOException | InterruptedException e)
        {
            e.printStackTrace();
        }
        finally{
            try{
                if(socketChannel!=null){
                    socketChannel.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
复制代码

Selector

Channel和Selector配合使用,必须先将Channel注册到Selector上,经过register()方法来实现。Channel.register()方法会返回一个SelectionKey对象。这个对象表明了注册到该Selector的通道。

一旦向Selector注册了一个或多个通道,就能够调用select()方法。

select()方法返回的int值表示有多少通道已经就绪。

参考资料:疯狂Java讲义

Java NIO

Java BIO/NIO/AIO学习

相关文章
相关标签/搜索