java NIO,BIO,AIO理解和学习

java io学习

1 什么是io

我们常说的io是文件或字符的输入(input)和输出(output)

2 BIO,NIO,AIO 有什么区别
2.1 BIO
什么是BIO? 
同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。
但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。

一个java BIO实例如下
服务端代码展示

public class BioServerDemo {

    public static void main(String[] args) {
        int port = 9999;
        try {
            // 创建 bio socket服务
            ServerSocket serverSocket = new ServerSocket();
            // 绑定服务端口号
            serverSocket.bind(new InetSocketAddress(port));
            while (true){
                // 阻塞获得客户端socket
               Socket socket = serverSocket.accept();
               // 进入自定义的handle方法,处理客户端的socket
               new Thread(()-> handle(socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 自定义handle方法,处理服务端和客户端的socket
    private static void handle(Socket socket) {
        byte[] bytes = new byte[1024];
        try {
            // 获得输入流,读出客户端发送的数据
            int len = socket.getInputStream().read(bytes);
            System.out.println(new String(bytes,0,len));
            // 获得输出流,回写给客户端数据
            socket.getOutputStream().write("this is server".getBytes());
            socket.getOutputStream().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端代码展示

public class BioClientDemo {

    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 9999;
        try {
            Socket socket = new Socket(host,port);
            // 获得输出流,向服务端写数据
            socket.getOutputStream().write("hello,this is client".getBytes());
            socket.getOutputStream().flush();
            // 获得输入流,读取服务端给客户端的数据
            byte[] bytes = new byte[1024];
            int len = socket.getInputStream().read(bytes);
            System.out.println(new String(bytes,0,len));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
	优点: 模型简单,编码简单
	缺点:性能较低
	小结:
	1 BIO 主要体现在 获得客户端连接socket的时候,在阻塞accept,在一个socket中进行读写时,也会一直阻塞,会一直等待这个线程处理完结束
	2 每个连接都会创建一个线程,随着线程数量的增加,CPU切换线程上下文的消耗也随之增加,在高过某个阀值后,继续增加线程,性能不增反降
	3 因为一个连接创建一个线程,模型比较简单,适用于客户端连接教少的场景,可以进行简单处理
2.2 NIO
什么是NIO?
NIO 称为 Non-Block IO,	它支持面向缓冲的,基于通道的I/O操作方法。
NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。
阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。
含有选择器,选择器用于使用单个线程处理多个通道。
它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 
为了提高系统效率选择器是有用的。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发


线程模型示例
会有一个线程作为selector(选择器)进行处理所有的客户端,通过事件监听的形式处理socketChannel

java NIO,BIO,AIO理解和学习_第1张图片

java NIO 单线程实现
服务端代码

public class NioServerDemo {
    public static void main(String[] args) throws IOException {
        int port = 9999;
        // 创建 nio server
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        System.out.println("server start");
        // 获得selector
        Selector selector = Selector.open();
        // 把selector和accept事件注册到server中
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true){
            selector.select();
            // 获得selector管理的所有事件
            Set<SelectionKey> selectors = selector.selectedKeys();
            // 迭代selector
            Iterator<SelectionKey> selectionKeyIterable = selectors.iterator();
            while (selectionKeyIterable.hasNext()){
                SelectionKey key = selectionKeyIterable.next();
                // 处理每一个 selectionKey
                handle(key);
                // 处理完之后移除
                selectionKeyIterable.remove();
            }
        }
    }

    private static void handle(SelectionKey key){
        // 处理accept事件
        if (key.isAcceptable()){
            // 获得serversocket channel,处理对客户端连接的socket
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            try {
                SocketChannel socketChannel = serverSocketChannel.accept();
                serverSocketChannel.configureBlocking(false);
                // 注册读事件
                serverSocketChannel.register(key.selector(),SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else if (key.isReadable()){
            // 处理读事件
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(512);
            byteBuffer.clear();
            int len = 0;
            try {
                len = socketChannel.read(byteBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (len != -1){
                System.out.println(new String(byteBuffer.array(),0,len));
            }
            // 向客户端写入数据
            ByteBuffer bufferWrite = ByteBuffer.wrap("hello,this is server".getBytes());
            try {
                socketChannel.write(bufferWrite);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

多线程实现

public class NioThreadPoolServerDemo {

    private ExecutorService executorService = Executors.newFixedThreadPool(20);
    private Selector selector;
    public static void main(String[] args) throws IOException {
        int port = 9999;
        NioThreadPoolServerDemo nioThreadPoolServerDemo = new NioThreadPoolServerDemo();
        nioThreadPoolServerDemo.initServer(port);
        nioThreadPoolServerDemo.listen();
    }
    
    private void initServer(int port) throws IOException {
        // 创建bio 服务
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        // 开启selector 模型
        this.selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    private void listen() throws IOException {
        while (true){
            selector.select();
            Set<SelectionKey> selectionKeySet = selector.selectedKeys();
            Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
            if (selectionKeyIterator.hasNext()){
                SelectionKey key = selectionKeyIterator.next();
                selectionKeyIterator.remove();
                // 处理accept事件
                if (key.isAcceptable()){
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel sc = server.accept();
                    sc.configureBlocking(false);
                    // 注册read事件
                    sc.register(this.selector,SelectionKey.OP_READ);
                }else if (key.isReadable()){
                    key.interestOps(SelectionKey.OP_READ);
                    // 有数据读写时,交给线程池处理,
                    executorService.execute(new ThreadHandlerChannel(key));
                }
            }
            
            
        }
    }
}
class ThreadHandlerChannel extends Thread{
    private SelectionKey key;
    ThreadHandlerChannel(SelectionKey key){
        this.key = key;
    }

    @Override
    public void run() {
        // 处理读事件
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        byteBuffer.clear();
        int len = 0;
        try {
            len = socketChannel.read(byteBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (len != -1){
            System.out.println(new String(byteBuffer.array(),0,len));
        }
        // 向客户端写入数据
        ByteBuffer bufferWrite = ByteBuffer.wrap("hello,this is server".getBytes());
        try {
            socketChannel.write(bufferWrite);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
	小结
	1 通过选择器selector进行事件监听
	2 需要主动把socket事件注册到选择器中
2.3 AIO (asynchronius)
	AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。
	异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
	AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。
	对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。
public class AioServerDemo {

    public static void main(String[] args) throws IOException {
        int port = 9999;
        AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress(port));
        // 当有客户端连接时,accept不再阻塞线程,交给CompletionHandler 进行处理
        serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {

            @Override
            public void completed(AsynchronousSocketChannel client, Object attachment) {
                serverSocketChannel.accept(null,this);
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                // 读数据也不再阻塞执行,读完数据后交给CompletionHandler处理
                client.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result, ByteBuffer attachment) {
                        attachment.flip();
                        client.write(ByteBuffer.wrap("this is server".getBytes()));
                    }
                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {}
                });
            }
            @Override
            public void failed(Throwable exc, Object attachment) {}
        });
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

小结:
1 NIO和AIO在linux底层都是用epoll实现
2 windows上AIO用的是系统实现
3 使用场景
1 BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
2 NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
3 AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

你可能感兴趣的:(java,java)