[笔记]BIO、NIO、AIO

简述

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 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)