zoukankan      html  css  js  c++  java
  • BIO、NIO、AIO 个人总结

    BIO(blocking io)

    BIO即为阻塞IO,在网络编程中,它会在建立连接和等待连接的对端准备数据阶段进行阻塞。因此为了支撑高并发的用户访问,一般会为每一个socket 连接分配一个线程。但使用的瓶颈更加明显,无法支持上百万、甚至千万以上的并发。且线程切换带来的开销也更大。

    BIO.png

    代码示例:

    Server端

    1. Server 端绑定 8082 端口
    2. 通过 accept() 方法 阻塞等待客户端建立连接
    3. 当与客户端建立一个 socket 连接通道之后,server 端口将新建一个线程进行 【读】 、【写】
      ( ps: 这里的读是阻塞的,因为当server端发起读的请求时,如果此时对端未准备好数据,那么将一直阻塞等待 。
      直到:1. 对端通过 socket 发送数据2. 将数据从内核态 拷贝到用户态
      )
    
    try {
                // 绑定本地 8082 端口
                ServerSocket server = new ServerSocket(8082);
                while (true) {
                    //阻塞等待客户端 socket 建立连接
                    Socket accept = server.accept();
                    Thread t = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            // 一个线程处理一个socket 连接
                            BufferedReader reader = null;
                            PrintWriter out = null;
                            try {
                                reader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
                                String readLine = reader.readLine();
                                System.out.println("receive message is " + readLine);
                                out = new PrintWriter(accept.getOutputStream(), true);
                                out.println("接收完毕,请继续发送!");
                            } catch (IOException e) {
                                e.printStackTrace();
                            } finally {
                                if (accept != null) {
                                    try {
                                        accept.close();
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    });
                    t.start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    

    Client端

    1. 与 server 建立连接的过程,会阻塞
      2.建立连接之后 ,便可通过获取 socket 的 输入、输出流进行读写
      (ps : 与 server 端原因相同,读的时候也会阻塞)
            //阻塞等待连接建立
            Socket socket = new Socket("localhost", 8082);
            //处理数据
            BufferedReader in = null;
            PrintWriter out = null;
            try {
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream(), true);
                out.println("Now establish connection");
                System.out.println("从 server 端收到连接信息:" + in.readLine());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                socket.close();
            }
    

    NIO (no blocking io 也叫 new io)

    NIO 即非阻塞IO,是JDK 1.4 更新的api, 核心内容是 将建立连接、数据可读、可写等事件交给了操作系统来维护, 通过调用操作系统的 api (如:select、epoll等),来判断当前是否支持:可读、可写,如果当前不可操作,那么直接返回,从而实现了非阻塞。 而不需要像 BIO 那样每次去轮询等待连接的建立以及数据的准备是否完成。主要核心的模块分以下几类:

    1. 缓冲区Buffer

    一个特定基类(byte、short、int、long 等)的数据容器,用作在建立socket 连接之后的数据传输。
    通过 capacity, limit, position,mark 指针来实现数据的读写

    get()、put() 方法为每个子类都具有的读、写数据的api方法,当从当前的 position 读或写的同时,position会增加 相应读写的数据的长度。当position 达到limit 之后,再次 get、put则会抛出异常

    2. Channel 连接通道

    一个 channel 代表一个与“实体”的连接通道,如:硬件设备、文件、网络 socket 。通过连接通道可以使得客户端-服务器互相传输数据,因此通道也是全双工的(因为是建立在TCP 传输层的协议上,因此具备全双工的能力)。

    JDK 中 channel 可以分为以下几类:

    • SelectableChannel 用于 阻塞和非阻塞 socket 连接的通道
    • FileChannel 用于文件操作,包括:reading, writing, mapping, and manipulating a file

    3.Selector 多路复用选择器

    用于 SelectableChannel 的多路复用器,当使用非阻塞的 socket 时,需要将监听的通道 SelectableChannel 感兴趣的事件注册到 selector 多路复用器上(selector 实际上是通过调用操作系统层面的 select、epoll 方法来获取当前可用的时间)

    与之对应的感兴趣的事件用 SelectionKey 来表示

    • OP_READ = 1 << 0; 可读
    • OP_WRITE = 1 << 2; 可写
    • OP_CONNECT = 1 << 3; // 完成连接
    • OP_ACCEPT = 1 << 4; // 接收连接

    处理流程图:

    NIO .png

    代码示例:

    1. 通过 ServerSocketChannel 监听 8082 端口
    2. 设置为非阻塞
    3. 选择与操作系统适配的选择器,serverSocketChannel 的 OP_ACCEPT 事件注册到 selector 选择器上
    4. 当OP_ACCEPT 事件触发时,将所有建立好的Socketchannel 连接的感兴趣的事件(这里为 read事件)再次注册到Selector 上
            // 1.根据操作系统选择适当的底层 io复用方法
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8082));
            //2.设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            //3.选择与操作系统适配的选择器
            Selector selector = Selector.open();
            //将 serverSocket 的OP_ACCEPT 事件注册到 selector 选择器上
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                // 4.监听当前连接建立情况
                int select = selector.select();
                if (select > 0) {
                    //判断连接业务类型
                    Set<SelectionKey> set = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = set.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        //建立连接
                        if (key.isAcceptable()) {
                            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                            //通过 accept 方法获取与 server端 已经创建好的 socket连接
                            SocketChannel sc = ssc.accept();
                            //设置为非阻塞
                            sc.configureBlocking(false);
                            //注册感兴趣的事件为 READ
                            sc.register(selector, SelectionKey.OP_READ);
                        }
                        //可读
                        else if (key.isReadable()) {
                            SocketChannel socket = (SocketChannel) key.channel();
                            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                            socket.read(byteBuffer);
                            System.out.println(new String(byteBuffer.array(), StandardCharsets.UTF_8));
                            key.interestOps(SelectionKey.OP_WRITE);
                        }
                        //可写
                        else if (key.isWritable()) {
                            SocketChannel socket = (SocketChannel) key.channel();
                            socket.write(ByteBuffer.wrap("I'm receive your message".getBytes(StandardCharsets.UTF_8)));
                            socket.close();
                            System.out.println("连接关闭成功!");
                        }
                    }
                }
            }
    

    AIO(asynchronous io)

    NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
        异步的套接字通道时真正的异步非阻塞I/O,对应于UNIX网络编程中的事件驱动I/O(AIO)。他不需要过多的Selector对注册的通道进行轮询即可实现异步读写,从而简化了NIO的编程模型。

    代码示例

       private static void server() throws IOException {
            //根据操作系统建立对应的底层操作类
            AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
            channel.bind(new InetSocketAddress(8082));
            while (true) {
                Future<AsynchronousSocketChannel> future = channel.accept();
                try {
                    AsynchronousSocketChannel asc = future.get();
                    System.out.println("建立连接成功");
                    Future<Integer> write = asc.write(ByteBuffer.wrap("Now let's exchange datas".getBytes(StandardCharsets.UTF_8)));
                    while (!write.isDone()) {
                        TimeUnit.SECONDS.sleep(2);
                    }
                    System.out.println("发送数据完成");
                    asc.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        private static void client() throws Exception {
            AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
            Future<Void> future = socketChannel.connect(new InetSocketAddress(8082));
            while (!future.isDone()) {
                TimeUnit.SECONDS.sleep(2);
            }
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            Future<Integer> read = socketChannel.read(buffer);
            while (!read.isDone()) {
                TimeUnit.SECONDS.sleep(2);
            }
            System.out.println("接收服务器数据:" + new String(buffer.array(), 0, read.get()));
        }
    
  • 相关阅读:
    PriorityQueue详解
    Queue介绍
    Dubbo面试踩坑
    Java中Integer和ThreadLocal
    Java中Deque特性及API
    由ubuntu装好想到的
    双系统试水上岸
    终于意识到BIM确实火了
    读阿里机器学习平台的一些总结
    继续ubuntu和遇到的easybcd的坑
  • 原文地址:https://www.cnblogs.com/coding400/p/11395185.html
Copyright © 2011-2022 走看看