NIO机制深入及代码示例

来源:互联网 发布:2017日剧 知乎 编辑:程序博客网 时间:2024/05/16 05:53

NIO:将通道绑定端口,并且将通道注册到selector并且监听特定事件

与外围设备数据通信需要channel通道,如像外部输出数据,先将数据写入缓冲区通过通道输出。如果从外部读取数据,需要通过通道channel读取到缓冲区,然后从缓冲区数据获取处理。


NIO底层工作机制:

Select 模型  的底层实现可以通过服务提供者配置,支持的方式有select、poll、epoll。在注册了感兴  趣的I/O 事件给操作系统内核后,便可以继续其它的操作。内核会通知这些事件的发生,  改变SelectionKey 维持的这些I/O 事件的状态,然后使用select/poll/epoll 方式便可以将  这些SelectionKey 提取出来,从而获取它们所绑定的SelectableChannel 类型通道,从而  在数据准备就绪的情况下进行I/O 操作。

Channel& Selector:
Channel 和 Selector,它们是 NIO 中两个核心概念。我们还用前面的城市交通工具来继续比喻 NIO 的工作方式,这里的 Channel 要比 Socket 更加具体,它可以比作为某种具体的交通工具,如汽车或是高铁等,而 Selector 可以比作为一个车站的车辆运行调度系统,它将负责监控每辆车的当前运行状态:是已经出战还是在路上等等,也就是它可以轮询每个 Channel 的状态。这里还有一个 Buffer 类,它也比 Stream 更加具体化,我们可以将它比作为车上的座位,Channel 是汽车的话就是汽车上的座位,高铁上就是高铁上的座位,它始终是一个具体的概念,与 Stream 不同。Stream 只能代表是一个座位,至于是什么座位由你自己去想象,也就是你在去上车之前并不知道,这个车上是否还有没有座位了,也不知道上的是什么车,因为你并不能选 择,这些信息都已经被封装在了运输工具(Socket)里面了,对你是透明的。NIO 引入了 Channel、Buffer 和 Selector 就是想把这些信息具体化,让程序员有机会控制它们,如:当我们调用 write() 往 SendQ 写数据时,当一次写的数据超过 SendQ 长度是需要按照 SendQ 的长度进行分割,这个过程中需要有将用户空间数据和内核地址空间进行切换,而这个切换不是你可以控制的。而在 Buffer 中我们可以控制 Buffer 的 capacity,并且是否扩容以及如何扩容都可以控制。:

Selector内部原理:实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙(SelectionKey表示 SelectableChannel 在 Selector 中的注册的标记。 )来读取这个channel的内容

缓冲区内部细节

初始化

第一次读取3个字节到缓冲区

第二次读取2个字节到缓冲区

 调用Flip,limit设置positionposition设置为0 ,limit下一个不可用的索引

  5 调用clear,将limit设置为capacityposition设置为0

NIO核心观念:通道和缓冲区(channelBuffer

1)         块操作

2)         非阻塞IO

复用机制采用select模型,注册感兴趣IO事件给操作系统后,一旦这些事件发生,内核会改变这些事件的状态,通过检查这些事件的状态,便可以查找到注册过的、准备就绪的I/O 事件,然后可以流畅地进行I/O操作。


传统IO阻塞调用:

流 I/O 是阻塞调用的。不论是read 方法还是write 方法都能阻塞(block)一个线程直到字节被真正地读取或者写入。这意味着如果流不能立即被读取或者写入字节(通常是因为网络连接繁忙),Java 就会挂起这个调用的线程,处于等待的状态。常用Socket 的读写操作都是阻塞式的,就是说每次read/write 掉用,在数据被读入/写出前,调用线程都处于阻塞的状态.

 

多线程耗费资源,特别是多线程间切换很耗费资源。

 

NIO特征:

l)面向块的I/O 操作

2)非阻塞的I/O 操作

3)字符集编码解码

4)内存映射文件

5)文件锁定

Reactor 模型:

Reactor 模型采用分而治之的思想,将一个客户端连接的事件分成两类:I/O 事件和非I/O 事件。前者需要等待I/O 准备就绪,后者可以立即执行,因此分别进行处理。I/O 事件包括:Read(读取请求信息)Send(发送响应信息)。非I/O 事件包括encodecomputedecode

 

异步连接池:

首先,用户处理线程调用连接池对象的某个方法(比如sendRequest),把一个能够标识本次请求的Request 对象扔给连接池。之后用户处理线程可以去做别的事情,比如,向其他连接池发送请求。最后当用户线程处理完能做的业务逻辑后,就可以等待连接池返回结果了。

 

(一) 读写IO:(按块缓冲区)

package java.nio.io;

 

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.MappedByteBuffer;

import java.nio.channels.FileChannel;

import java.util.Date;

 

/** 

 * @Project: NIO

 * @Title: CopyFile.java

 * @Package

 * @Description: JAVA NIO 读取写入文件方面优势

 * @author paul.wei2011@gmail.com

 * @date May 16, 2013 9:51:13 AM

 * @version V1.0 

 */

 

//传统IO一直是性能瓶颈,因为每次按字节读取,耗费磁头寻址次数,磁头寻址时间是固定的,次数多耗费时间就长

public class CopyFile {

    public static void main(String[] args) throws Exception {  

        String infile = "C:\\copy.sql";  

        String outfile = "C:\\copy.txt";  

        // 获取源文件和目标文件的输入输出流  

        FileInputStream fin = new FileInputStream(infile);  

        FileOutputStream fout = new FileOutputStream(outfile);  

        // 获取输入输出通道  

        FileChannel fcin = fin.getChannel();//FileChannel对象是线程安全的  

        FileChannel fcout = fout.getChannel();

        //   每个FileChannel都有一个叫'file position'的概念,该position值决定文件中哪一处的数据接下来被读或写。

        //   FileChannel position是从底层文件描述符获取的, 当字节被read( ) write( ) 方法传输时,文件position会自动更新。

        //   MappedByteBuffer类使得我们可以通过ByteBuffer API来访问数据文件

        byteBuffer(fcin,fcout); //2 

        mapByteBuffer(fcin,fcout);//1

    }

   

    // 创建缓冲区  

    public static void byteBuffer(FileChannel fcin,FileChannel fcout) throws IOException{

        ByteBuffer buffer = ByteBuffer.allocate(1024);  

//      MappedByteBuffer mapBuffer = fcin.map(FileChannel.MapMode.READ_ONLY, 0,fcin.size());

        Date start = new Date();

        while (true) {  

            // clear方法重设缓冲区,使它可以接受读入的数据  

            buffer.clear();  

            // 从输入通道中将数据读到缓冲区  

            int r = fcin.read(buffer);  

            // read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1  

            if (r == -1) {  

                break;  

            }  

            // flip方法让缓冲区可以将新读入的数据写入另一个通道  ,position赋值给limit,position=0

            buffer.flip();  

            // 将缓冲区内容写入输出通道

            fcout.write(buffer);  

        }  

        Date end = new Date();

        System.out.println("150M byteBuffer spend:"+(end.getTime()-start.getTime())/1000+" sec");//150M 4

    }

   

    // 创建映射缓冲区  ,把文件的内容被映像到计算机虚拟内存的一块区域,

    // 这样就可以直接操作内存当中的数据而无需操作的时候每次都通过I/O去物理硬盘读取文件

    public static void mapByteBuffer(FileChannel fcin,FileChannel fcout) throws IOException{

        MappedByteBuffer mapBuffer = fcin.map(FileChannel.MapMode.READ_ONLY, 0,fcin.size());

        Date start = new Date();

        fcout.write(mapBuffer);//将映射缓冲区直接写入输出通道

        Date end = new Date();

        System.out.println("150M mapByteBuffer spend:"+(end.getTime()-start.getTime())/1000+" sec");//150M 4

    }

}

(二) 异步IO:

    传统IO:

服务端:

public class SynchServerSocket {

     private int port=8821;

     private ServerSocket serverSocket;

     private ExecutorService executorService;//线程池

     private final int POOL_SIZE=10;//单个CPU线程池大小

     public SynchServerSocket() throws IOException{

     serverSocket = new ServerSocket(port);

     //RuntimeavailableProcessor()方法返回当前系统的CPU数目.

         executorService=Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*POOL_SIZE);

         System.out.println("服务器启动");

     }

    

     public void service(){

     while(true){

           Socket socket=null;

           try{

          //接收客户连接,只要客户进行了连接,就会触发accept();从而建立连接,

          //如果客户端未连接,服务端一直阻塞I/O等待连接,多线程从而耗费CPU及栈资源

             socket=serverSocket.accept();

             executorService.execute(new Handler(socket));

           }catch(Exception e){

               e.printStackTrace();

           }

     }

     }

     public static void main(String[] args) throws IOException {

         new SynchServerSocket().service();//服务启动一直等待连接

     }

 

 

}

 

class Handler implements Runnable{

    private Socket socket;

     public Handler(Socket socket){

        this.socket=socket;

    }

     private PrintWriter getWriter(Socket socket) throws IOException{ //根据socket 获取输出流

        OutputStream socketOut=socket.getOutputStream();

        return new PrintWriter(socketOut,true);

    }

     private BufferedReader getReader(Socket socket) throws IOException{//根据socket 获取输入流

         InputStream socketIn=socket.getInputStream();

         return new BufferedReader(new InputStreamReader(socketIn));

     }

     public String echo(String msg){

         return "echo:"+msg;

     }

     public void run(){

         try {

            System.out.println("New connection accepted "+socket.getInetAddress()+":"+socket.getPort());

            BufferedReader br=getReader(socket);

            PrintWriter pw=getWriter(socket);

            String msg=null;

             while((msg=br.readLine())!=null){

                System.out.println(msg);

                pw.println(echo(msg));

                if(msg.equals("bye"))

                    break;

            }

         } catch (IOException e) {

            e.printStackTrace();

         }finally{

             try {

                if(socket!=null)

                    socket.close();

             } catch (IOException e) {

                e.printStackTrace();

            }

        }

      }

}

客户端:

public class SynchClientSocket {

    public static void main(String[] args) {

        int numTasks = 10;

        ExecutorService exec = Executors.newCachedThreadPool();

         for (int i = 0; i < numTasks; i++) {

            exec.execute(new Task(i));

        }

     }

}

class Task implements Runnable{ 

    private Socket socket = null;

    private int port=8821;

    public final int taskId=0;

    private int taskID;

    public Task(int taskID){

    this.taskID=taskID;

    }

    @Override

    public void run() {

         System.out.println("Task " + taskID + ":start");

         try {                   

            socket = new Socket("localhost"port);

            // 发送关闭命令

            OutputStream socketOut = socket.getOutputStream();

            socketOut.write("shutdown\r\n".getBytes());

            // 接收服务器的反馈

            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            String msg = null;

            while ((msg = br.readLine()) != null)

                System.out.println("response:"+msg);

         } catch (IOException e) {                   

            e.printStackTrace();

        }

    }

}

 

    异步IO(多路复用,通知模式)

服务端:

public class AsynServer implements Runnable{

     private ByteBuffer r_buff = ByteBuffer.allocate(1024);

     private ByteBuffer w_buff = ByteBuffer.allocate(1024);

     private static int port = 8848;

     public AsynServer(){

      new Thread(this).start();

     }

    @Override

    public void run() {

        try{

        //多路复用:Selector 各种IO事件注册到Selector,事件发生通知,不同通道对象注册到Selector

        Selector selector = Selector.open();

        //生成一个服务端口通道ssc

        ServerSocketChannel ssc = ServerSocketChannel.open();

        //将侦听端设为异步方式

        ssc.configureBlocking(false);

        //将服务端口通道ssc绑定一个端口

        ssc.socket().bind(new InetSocketAddress(port));

       //将服务端口通道注册到selector上监听事件为OP_ACCEPT信号

       ssc.register(selector,SelectionKey.OP_ACCEPT);

       System.out.println("echo server has been set up ......");

  

       while(true){

           int n = selector.select();

            if (n == 0) {//没有指定的I/O事件发生(监听是否有数据读或写)n>0,才会有selectedKeys

             continue;

            }    

            //当有读或写等任何注册的事件发生 时,可以从Selector 中获得相应的SelectionKey,同时从SelectionKey

            //中可以找到发生的事件和该事件所发生的具体的SocketChannle继承自SelectableChannel,以获得客户端发送过来的数据。

            Iterator it = selector.selectedKeys().iterator();           

            while (it.hasNext()) {

             SelectionKey key = (SelectionKey)it.next();

             if (key.isAcceptable()) {//侦听端信号触发

              ServerSocketChannel server = (ServerSocketChannel) key.channel();

              //接受一个新的连接

              SocketChannel sc = server.accept();

              sc.configureBlocking(false);

              //设置该socket的异步信号OP_READ:socket可读时,

             //触发函数DealwithData();

              sc.register(selector,SelectionKey.OP_READ);

             }else{

             if (key.isReadable()) {//socket可读信号

                  DealwithData(key);

                 }    

                 it.remove();

 

             }

             // 删除处理过的选择键

             it.remove();

 

            }

       }

    }catch(Exception e){

    }

    }

   

     public void DealwithData(SelectionKey key) throws IOException{

          int count;

          //key获取指定socketchannel的引用

          SocketChannel sc = (SocketChannel)key.channel();

          r_buff.clear();

          //读取数据到r_buff

          while((count = sc.read(r_buff))> 0);

          //确保r_buff可读

          r_buff.flip();

          //清空写缓存

          w_buff.clear();

          //r_buff内容拷入w_buff 

          w_buff.put(r_buff);

          w_buff.flip();//从写缓冲区向输出通道写之前调用flip

          EchoToClient(sc);

          w_buff.clear();

          r_buff.clear();

     }

     public void EchoToClient(SocketChannel sc) throws IOException{

          while(w_buff.hasRemaining())

           sc.write(w_buff);

     }

 

     public static void main(String args[]){

          if(args.length > 0){

           port = Integer.parseInt(args[0]);

          }

          new AsynServer();

         }

}

客户端:

public class AsynClient {

    private SocketChannel sc;

     private final int MAX_LENGTH = 1024;

     private ByteBuffer r_buff = ByteBuffer.allocate(MAX_LENGTH);

     private ByteBuffer w_buff = ByteBuffer.allocate(MAX_LENGTH);

     private static String host ;

     private static int port = 8848;

     public AsynClient(){

          try {

           InetSocketAddress addr = new InetSocketAddress(host,port);

           //生成一个socketchannel

           sc = SocketChannel.open();

                

           //连接到server

           sc.connect(addr);

           while(!sc.finishConnect())

            ; 

           System.out.println("connection has been established!...");

           

           while(true){

            //回射消息

            String echo;

            try{

             System.err.println("Enter msg you'd like to send:  ");

             BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

             //输入回射消息

             echo = br.readLine();

            

             //把回射消息放入w_buff   

             w_buff.clear();

             w_buff.put(echo.getBytes());

             w_buff.flip();

            }catch(IOException ioe){

             System.err.println("sth. is wrong with br.readline() ");

            }   

         

            //发送消息

            while(w_buff.hasRemaining())

             sc.write(w_buff);

            w_buff.clear();   

           

            //进入接收状态

            Rec();

            //间隔1

            Thread.currentThread().sleep(1000);

           } 

          }catch(IOException ioe){

           ioe.printStackTrace();

          }

          catch(InterruptedException ie){

           ie.printStackTrace();

          } 

         }

         public void Rec() throws IOException{

          int count;

          r_buff.clear();

          count=sc.read(r_buff);

          r_buff.flip(); 

          byte[] temp = new byte[r_buff.limit()];

          r_buff.get(temp);

          System.out.println("reply is " + count +" long, and content is: " + new String(temp));

         }

         public static void main(String args[]){

          if(args.length < 1){//输入需有主机名或IP地址

           try{

            System.err.println("Enter host name: ");

            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

            host = br.readLine();

           }catch(IOException ioe){

            System.err.println("sth. is wrong with br.readline() ");

           }

          }

          else if(args.length == 1){

           host = args[0];

          }

          else if(args.length > 1){

           host = args[0];

           port = Integer.parseInt(args[1]);

          }

          new AsynClient();

         }

}


0 0