ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

网络IO模型对比(BIO、NIO、AIO)

2022-02-09 09:03:49  阅读:181  来源: 互联网

标签:BIO NIO buffer AIO key new public channel 客户端


IO概述

软件开发的核心是数据,而数据的传输、保存、读取都是通过IO技术实现的。

Java中的IO模型主要有三种:

  • BIO 同步阻塞式IO
  • NIO 同步非阻塞式IO
  • AIO 异步非阻塞式IO

BIO

Blocking IO 同步阻塞式IO,是比较常用的IO模型

特点是

  • 编写相对简单
  • 分为输入流和输出流
  • 进行网络通讯时,输入流的读操作会阻塞住线程,直到有输出流执行写操作

一旦服务器的线程阻塞,服务器就不能处理其他客户端的业务,为解决这种问题,一般我们会为每个客户端单独启动线程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h9mvNdEG-1644367289453)(BIO&NIO&AIO.assets/1642923364463.png)]

这样造成了新的问题,就是如果客户端比较多,就会大量消耗服务器的线程资源,导致服务器压力过大。

所以这种模型比较适合连接数不大、数量相对固定的情况。

下面是使用BIO模型的Socket进行网络通信

/**
 * BIO服务器
 */
public class SocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            System.out.println("等待连接。。");
            //阻塞方法
            Socket socket = serverSocket.accept();
            System.out.println("有客户端连接了。。");
            //启动线程处理客户端IO
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        handler(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    /**
     * 处理客户端IO
     * @param socket
     * @throws IOException
     */
    private static void handler(Socket socket) throws IOException {
        System.out.println("thread id = " + Thread.currentThread().getId());
        byte[] bytes = new byte[1024];

        System.out.println("准备read。。");
        //接收客户端的数据,没有数据可读时就阻塞
        int read = socket.getInputStream().read(bytes);
        if (read != -1) {
            System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
        }
        //给客户端返回数据
        socket.getOutputStream().write("你好,客户端".getBytes());
        socket.getOutputStream().flush();
    }
}
/**
 * 客户端
 */
public class SocketClient {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 9000);
        //向服务端发送数据
        socket.getOutputStream().write("服务器你好!!".getBytes());
        socket.getOutputStream().flush();
        System.out.println("向服务端发送数据结束");
        byte[] bytes = new byte[1024];
        //接收服务端回传的数据
        socket.getInputStream().read(bytes);
        System.out.println("接收到服务端的数据:" + new String(bytes));
        socket.close();
    }
}

NIO

jdk1.4出现了NIO(Noblocking IO 同步非阻塞式IO) ,与BIO不同的是,NIO进行读取操作时,不会阻塞线程

NIO有三大核心组件:

  • Channel 通道,类似BIO的输入输出流的作用,不同IO流的是Channel同时可以完成读写操作
  • Buffer 缓冲区,类似BIO中的byte数组,用于存放数据
  • Selector 多路复用器,每个Channel都会注册到selector上,selector会在多个通道间轮询,一旦有读写事件出现就会立即进行处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XT1oh1Pp-1644367289455)(BIO&NIO&AIO.assets/1642924761487.png)]

优点:单线程就可以处理大量的IO请求,这样就大大降低了服务器的压力。

缺点:如果每个IO请求处理时间比较长,会产生长时间排队的情况;编程也比较复杂。

比较适合处理连接数比较多并且时间较短的场景,如:聊天、发弹幕等。

服务器

public class NIOServer {

    public static void main(String[] args) throws IOException {
        // 创建一个在本地端口进行监听的服务Socket通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //必须配置为非阻塞才能往selector上注册
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(9000));
        // 创建选择器
        Selector selector = Selector.open();
        // 注册到选择器上,对客户端连接操作感兴趣
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            // 轮询监听channel里的key,select是阻塞的
            selector.select();
            System.out.println("事件发生");
            // 有客户端请求,被轮询监听到
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //删除本次已处理的key,防止下次select重复处理
                it.remove();
                handle(key);
            }
        }
    }

    /**
     * 事件处理
     * @param key
     * @throws IOException
     */
    private static void handle(SelectionKey key) throws IOException {
        //处理连接事件
        if (key.isAcceptable()) {
            System.out.println("处理连接事件");
            //获得ServerSocketChannel
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //获得ServerSocketChannel接收的客户端
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            //注册对SocketChannel读事件感兴趣
            sc.register(key.selector(), SelectionKey.OP_READ);
        }
        //处理读取事件
        else if (key.isReadable()) {
            System.out.println("处理读取事件");
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //read方法不会阻塞,调用时肯定是发生了客户端发送数据的事件
            int len = sc.read(buffer);
            if (len != -1) {
                System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("Hello 客户端".getBytes());
            sc.write(bufferToWrite);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        }
        //处理写事件
        else if (key.isWritable()) {
            SocketChannel sc = (SocketChannel) key.channel();
            System.out.println("处理写事件");
            key.interestOps(SelectionKey.OP_READ);
        }
    }
}

客户端

public class NIOClient {
    //多路复用器
    private Selector selector;

    /**
     * 启动客户端测试
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NIOClient client = new NIOClient();
        client.initClient("127.0.0.1", 9000);
        client.connect();
    }

    /**
     * 获得一个Socket通道,并对该通道做一些初始化的工作
     *
     * @param ip   连接的服务器的ip
     * @param port 连接的服务器的端口号
     * @throws IOException
     */
    public void initClient(String ip, int port) throws IOException {
        // 获得一个Socket通道
        SocketChannel channel = SocketChannel.open();
        // 设置通道为非阻塞
        channel.configureBlocking(false);
        // 获得一个通道管理器
        this.selector = Selector.open();

        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
        //用channel.finishConnect() 才能完成连接
        channel.connect(new InetSocketAddress(ip, port));
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     *
     * @throws IOException
     */
    public void connect() throws IOException {
        // 轮询访问selector
        while (true) {
            selector.select();
            // 获得selector中选中的项的迭代器
            Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                // 删除已选的key,以防重复处理
                it.remove();
                // 连接事件发生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    // 如果正在连接,则完成连接
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();
                    }
                    // 设置成非阻塞
                    channel.configureBlocking(false);
                    //在这里可以给服务端发送信息哦
                    ByteBuffer buffer = ByteBuffer.wrap("HelloServer".getBytes());
                    channel.write(buffer);
                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);                                            // 获得了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }
            }
        }
    }

    /**
     * 处理读取服务端发来的信息 的事件
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        //和服务端的read方法一样
        // 服务器可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = channel.read(buffer);
        if (len != -1) {
            System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
        }
    }
}

AIO

AIO(异步非阻塞),由操作系统完成后回调通知服务端程序启动线程去处理

适合于连接数多且时间较长的操作,JDK1.7开始

服务器

public class AIOServer {
    public static void main(String[] args) throws Exception {
        //打开异步服务器通道,绑定端口
        final AsynchronousServerSocketChannel serverChannel =
                AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));
        //设置服务器接收连接回调
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            //连接成功
            @Override
            public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
                try {
                    // 接收客户端连接
                    serverChannel.accept(attachment, this);
                    System.out.println(socketChannel.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //设置读取客户端数据的回调
                    socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        //读取成功
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            buffer.flip();
                            System.out.println(new String(buffer.array(), 0, result));
                            socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer buffer) {
                            exc.printStackTrace();
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //连接失败
            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });

        Thread.sleep(Integer.MAX_VALUE);
    }
}

客户端

public class AIOClient {

    public static void main(String[] args) throws Exception {
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get();
        socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
        ByteBuffer buffer = ByteBuffer.allocate(512);
        Integer len = socketChannel.read(buffer).get();
        if (len != -1) {
            System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
        }
    }
}

BIO、NIO、AIO对比

BIONIOAIO
IO模型同步阻塞同步非阻塞异步阻塞
编程难度较低较高较高
可靠性
吞吐量

举个栗子:去餐厅吃饭等位子

BIO:拿号后在门口排队等候,什么都不做

NIO:拿号后去旁边逛街,每隔一段时间来看看有没有到自己

AIO:在手机上预订后,去旁边逛街,到自己后手机通知再过去

标签:BIO,NIO,buffer,AIO,key,new,public,channel,客户端
来源: https://blog.csdn.net/u013343114/article/details/122834825

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有