美文网首页
[笔记]BIO、NIO、AIO

[笔记]BIO、NIO、AIO

作者: 蓝灰_q | 来源:发表于2017-10-26 14:41 被阅读85次

    简述

    BIO为同步阻塞BlockingIO,BIO适用于连接数小,结构固定的场景。
    NIO为同步非阻塞NoBlockingIO,NIO适用于连接数大,但是任务小的场景。
    AIO(NIO.2)为异步非阻塞,AIO适用于连接数大,且时间长的场景。
    实际上,主要的分类还是BIO与NIO。

    BIO及其经典写法

    BIO就是传统IO,用流的方式处理IO。
    所有的IO都被视为单个字节的移动,stream对象一次移动一个字节,流IO先把流转换为字节,再转换为对象。
    BIO的经典写法如下:
    读取输入流的写法

    URL url=new URL("http://...");
    InputStream is=url.openStream();//获取字节流
    InputStreamReader ir=new InputStreamReader(is,"UTF-8");//转为字符流
    BufferReader br=new BufferReader(ir);
    String data=br.readLine();
    while(data!=null){
      data=br.readLine();
    }
    br.close();
    ir.close();
    is.close();
    

    Socket的客户端写法

    Socket socket=new Socket(ip,port);//TCP握手建立连接
    OutpurStream os=socket.getOutputStream();//向服务端的输出流
    PrintWriter pw=new PrintWriter(os);//包装输出流
    pw.write("message");
    pw.flush();
    socket.shutdownOutput();
    //输入流
    InputStream is=socket.getInputStream();
    BufferReader br=new BufferReader(new InputStreamReader(is));
    String data=br.readLine();
    while(data!=null){
       data=br.readLine();
    }
    br.close();
    is.close();
    pw.close();
    os.close();
    socket.close();
    

    Socket的服务端写法

    ServerSocket serverSocket=new ServerSocket(port);
    Socket socket=serverSocket.accept();//开始监听,阻塞等待客户端的连接,TCP三次握手后,才能完成
    InputStream is=socket.getInputStream();
    InputStreamReader isr=new InputStreamReader(is);
    BufferReader br=new BufferReader(isr);
    String data=br.readLine();
    while(data!=null){
        data=br.readLine();
    }
    socket.shutdownInput();
    //用输出流向客户端返回信息
    OutputStream os=socket.getOutputStream();
    PrintWriter pw=new PrintWriter(os);
    pw.write("response");
    pw.flush();
    pw.close();
    os.close();
    br.close();
    isr.close();
    is.close();
    socket.close();
    

    NIO及其经典写法

    NIO不再是流的概念,而是以块的方式处理IO。
    不需要一点点地移动字节,这样每个线程只需要处理IO拿到的数据块,不需要等待IO,所以可以复用线程,更可以避免线程切换带来的上下文切换,提升效率。
    NIO需要有一个专门的线程处理所有的IO事件,是事件驱动的。

    以SocketIO为例,NIO的客户端写法如下:
    创建通道和管理器

    SocketChannel channel=SocketChannel.open();//通道
    channel.configBlocking(false);//非阻塞
    this.selector=Selector.open();//通道管理器
    channel.connect(new InetSocketAddress(ip,port));//需要通过channel.finishConnect才能完成连接
    channel.register(selector,SelectionKey.OP_CONNECT);//通道管理器监听
    

    轮询事件处理

    public void listen() throws IOException{
        while(true){
            Iterator itr=this.selector.selectKeys().iterator();
            while(itr.hasNext()){
               SelectionKey key=(SelectionKey)itr.next();
               itr.remove();
               if(key.isConnectable()){
                  SocketChannel channel=(SelectionKey)key.channel();
                  if(channel.isConnectionPending){
                     channel.finishConnect();
                  }
                  channel.configBlocking(false);
                  channel.write(ByteBuffer.wrap(new String("Message content'").getBytes()));
                  channel.register(this.selector,SelectionKey.OP_READ);
               }else if(key.isReadable()){
                 ...
               }
            }
        }
    }
    

    NIO的服务端写法如下:
    创建通道和管理器

    ServerSocketChannel channel=ServerSocketChannel.open();
    channel.configBlocking(false);
    this.selector=Selector.open();
    channel.socket().bind(new InetSocketAddress(port));//server端用bind,client端用connect
    channel.register(selector,SelectionKey.OP_ACCEPT);//注册感兴趣的事件
    

    处理事件

    public void listen() throws IOException{
      while(true){
        selector.select();//如果没有感兴趣事件,会一直阻塞
        Iterator itr=this.selector.selectKeys().iterator();
        while(itr.hasNext()){
    SelectionKey key=(SelectionKey)itr.next();
               itr.remove();
               if(key.isConnectable()){
                  SocketChannel channel=(SelectionKey)key.channel();
                  if(channel.isConnectionPending){
                     channel.finishConnect();
                  }
                  channel.configBlocking(false);
                  channel.write(ByteBuffer.wrap(new String("Message content'").getBytes()));
                  channel.register(this.selector,SelectionKey.OP_READ);
               }else if(key.isReadable()){
                 ...
               }
        }
      }
    }
    

    NIO详解

    NIO有三个核心对象Buffer、Channel、Selector,其中Channel代替了流,Buffer是流的容器对象,写入Channel需要先经过Buffer,读取Channel也需要先读进Buffer。

    Channel是直接读写数据的对象,但它不是流,Channel是双向的(操作系统底层通常都是双向的,所以Channel比流更贴近真实情况),可以异步读写,Channel不允许应用层直接操作。
    NIO中的Channel包括:
    1.SocketChannel TCP网络IO
    2.ServerSocketChannel 监听TCP连接
    3.DatagramChannel UDP网络IO
    4.FileChannel 文件IO

    Buffer是应用层的操作对象,应用层不能直接操作Channel层,只能操作Buffer层,Buffer是个中转池,实质是个数组,能对IO数据结构化访问,而且可以跟踪系统的读写进程。
    Buffer的读写功能包括:
    1.写入到Buffer
    2.从Buffer读出
    3.用filp切换读写模式
    4.用clear清空buffer
    5.用compact压紧,就是清除buffer中已经读过的数据,未读过的数据挪到开头。
    Buffer的flip,clear,compact等操作,实质都是设置数组的position、limit和capacity,position代表从哪里开始处理,limit代表处理到哪里,capacity代表容量长度。

    Selector是NIO的核心实现,是管理channel通道的对象,每个线程通过1个Selector管理多个Channel对象,Selector注册并监听多个Channel,根据监听事件决定Channel的读写。
    使用Selector,首先要打开Selector
    Selector selector=Selector.open();
    然后要注册到Channel
    channel.configueBlocking(false);//设置为异步模式
    SelectionKey key=channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    SelectionKey有OP_CONNECT、OP_ACCEPT、OP_READ、OP_WRITE四种。分别是成功连接,可以接收连接,可以读,可以写。
    SelectionKey可以提供Channel和Selector,可以继续附加对象,在selector中监控的所有Channel,都可以通过SelectionKey来间接得到,通过Set<SelectionKey> keys=selector.selectedKeys();可以得到数据集合,通过遍历这个集合,就能检查selector的所有channel。
    处理完channel后,我们需要自己手动把selectionKey从数据集中remove挪出来。

    epoll

    epoll是底层操作系统对NIO的支持机制,epoll用红黑树管理被监控的socket句柄文件,如果socket的读写中断到了,就放进一个准备就绪list链表,仅当链表有数据时,才返回,没有数据就继续等待。

    NIO比BIO的优势

    BIO是给每个连接分配一个线程,所以在需要大量连接时,就需要大量的线程,线程管理会带来额外开销。
    NIO是采用了通知机制等待连接返回数据,只有活动的IO才会占用线程,线程不会被IO阻塞,所以不需要大量的线程,相应的线程创建、销毁、切换等开销也可以省略,资源可以更加集中在业务处理上。NIO适合高并发场景。
    BIO的流以字节为单位,一次输入流产生一个字节,一次输出流消费1个自己,缺点是处理速度慢,优点是在字节上为流做过滤器简单方便。
    NIO的块以块为单位,每步操作产生或消费一个块,优点是处理速度快,缺点是在块上不能像流一样做简单方便的处理。
    BIO和NIO的Socket客户端与服务端写法都不同,BIO客户端是直接Socket socket=new Socket(ip,port),服务端是先建立ServerSocket serverSocket=new ServerSocket(port),然后Socket socket=serverSocket.accept();NIO客户端是channel.connect(new InetSocketAddress(ip,port)),服务端是channel.socket.bind(new InetSocketAddress(port));

    BIO和NIO在文件处理上的写法

    BIO和NIO不仅有网络IO操作,也有文件IO操作,不过文件IO操作不支持异步,无法设置connect.configBlocking(false)。

        //BIO文件拷贝
        public static void fileCopy(String source, String target) throws IOException {
            try (InputStream in = new FileInputStream(source)) {
                try (OutputStream out = new FileOutputStream(target)) {
                    byte[] buffer = new byte[4096];
                    int bytesToRead;
                    while((bytesToRead = in.read(buffer)) != -1) {
                        out.write(buffer, 0, bytesToRead);
                    }
                }
            }
        }
    
        //NIO文件拷贝
        public static void fileCopyNIO(String source, String target) throws IOException {
                //声明源文件和目标文件
                FileInputStream fi=new FileInputStream(new File(src));
                FileOutputStream fo=new FileOutputStream(new File(dst));
                //获得传输通道channel
                FileChannel inChannel=fi.getChannel();
                FileChannel outChannel=fo.getChannel();
                //获得容器buffer
                ByteBuffer buffer=ByteBuffer.allocate(1024);
                while(true){
                    //判断是否读完文件
                    int eof =inChannel.read(buffer);
                    if(eof==-1){
                        break;  
                    }
                    //重设一下buffer的position=0,limit=position
                    buffer.flip();
                    //开始写
                    outChannel.write(buffer);
                    //写完要重置buffer,重设position=0,limit=capacity
                    buffer.clear();
                }
                inChannel.close();
                outChannel.close();
                fi.close();
                fo.close();
    }
    

    引用

    深入浅出NIO Socket实现机制
    Java NIO 详解(一)
    Java NIO 详解(二)
    java IO 流Stream 序列化Serializable 文件File

    相关文章

      网友评论

          本文标题:[笔记]BIO、NIO、AIO

          本文链接:https://www.haomeiwen.com/subject/zeuyextx.html