NIO学习

NIO学习

ByteBuffer

ByteBuffer使用

  • 向buffer写入数据,调用channel.read(buffer)
  • 调用flip()切换至读模式
  • 从buffer读取数据,调用buffer.get()
  • 调用clear()或compact()切换成写模式
public static void main(String[] args) {
        // FileChannel
        // 1.输入输出流 2.RandomAccessFile
        try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {
            // 准备缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(10);
            while (true) {
                // 从channel读取数据,写入缓冲区
                int len = channel.read(buffer);
                log.debug("读取到的字节数 {}", len);
                if (len == -1) {
                    break;
                }
                // 打印缓冲区内容
                buffer.flip();//切换读模式
                while (buffer.hasRemaining()) { //是否还有剩余未读数据
                    byte b = buffer.get();
                    log.debug("实际字节 {}", (char)b);
                }
                // 切换为写模式
                buffer.clear();
            }
        } catch (IOException e) {
        }
    }

ByteBuffer结构

属性

  • capacity
  • position
  • limit(读写限制)

读模式下:limit为当前最大写入数量

写模式下:capacity=limit

网络编程

阻塞与非阻塞

阻塞线程(当没有获取到连接或者没有读取到数据时阻塞)

public static void main(String[] args) throws IOException {
        // 使用nio阻塞模式

        // 构建buffer
        ByteBuffer buffer = ByteBuffer.allocate(16);

        ServerSocketChannel open = ServerSocketChannel.open();

        //绑定端口
        open.bind(new InetSocketAddress(8080));

        // 建立连接的集合
        List<SocketChannel> channels = new ArrayList<>();

        while (true) {
            // accept 建立客户端连接,与客户端通信
            log.debug("connect...");
            SocketChannel src = open.accept();//阻塞方法,停止运行
            log.debug("connectted... {}", src);
            channels.add(src);
            for (SocketChannel channel : channels) {
                // 接收客户端信息
                log.debug("before read.. {}", channel);
                channel.read(buffer); // 阻塞方法,停止运行
                log.debug("after read.. {}", channel);
                buffer.flip();
                System.out.println(buffer);
                buffer.clear();
            }
        }
    }

非阻塞模式

public static void main(String[] args) throws IOException {
        // 使用nio阻塞模式

        // 构建buffer
        ByteBuffer buffer = ByteBuffer.allocate(16);

        ServerSocketChannel open = ServerSocketChannel.open();

        // 设置成非阻塞
        open.configureBlocking(false);

        //绑定端口
        open.bind(new InetSocketAddress(8080));

        // 建立连接的集合
        List<SocketChannel> channels = new ArrayList<>();

        while (true) {
            // accept 建立客户端连接,与客户端通信
            SocketChannel src = open.accept();//非阻塞,返回null
            if (src != null) {
                log.debug("connectted... {}", src);
                src.configureBlocking(false);//设置成非阻塞模式
                channels.add(src);
                for (SocketChannel channel : channels) {
                    // 接收客户端信息
                    int read = channel.read(buffer);// 非阻塞,线程不停止,未读到数据返回0
                    if (read > 0) {
                        log.debug("after read.. {}", channel);
                        buffer.flip();
                        System.out.println(buffer);
                        buffer.clear();
                    }
                }
            }
        }
    }

问题:CPU空转

Selector

public static void main(String[] args) throws IOException {
        // 创建selector,管理多个channel
        Selector selector = Selector.open();

        ServerSocketChannel open = ServerSocketChannel.open();

        open.configureBlocking(false);

        // 建立selector和channel之间的联系
        // selectionKey事件发生后,通过它得到事件和channel信息
        SelectionKey openKey = open.register(selector, 0, null);
        openKey.interestOps(SelectionKey.OP_ACCEPT);

        log.debug("register key : {}", openKey);

        open.bind(new InetSocketAddress(8080));

        while (true) {
           // selector方法,没有事件就阻塞,有事件继续运行
            // 事件未处理,不会阻塞,必须处理或者取消
            selector.select();

            // 处理事件,selectKey内部包含了所有发生的事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 需要删除selectionKey中的原色,防止下次处理会出现问题
                iterator.remove();
                log.debug("register key : {}", key);
                // 区分事件类型
                if (key.isAcceptable()) {
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.debug("{}", sc);
                } else if (key.isReadable()) {
                    // 触发事件的channel
                    try {
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(16);
                        int read = channel.read(buffer);// 正常断开的返回值是-1
                        if (read == -1) {
                            key.cancel();
                        } else {
                            buffer.flip();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();
                    }
                }
//                key.cancel();
            }
        }
    }

处理消息边界

  • 客户端和服务端约定固定长度
  • 使用两种不同长度接收

使用attach关联buffer(从而可以进行扩容操作)

public static void main(String[] args) throws IOException {
        // 创建selector,管理多个channel
        Selector selector = Selector.open();

        ServerSocketChannel open = ServerSocketChannel.open();

        open.configureBlocking(false);

        // 建立selector和channel之间的联系
        // selectionKey事件发生后,通过它得到事件和channel信息
        SelectionKey openKey = open.register(selector, 0, null);
        openKey.interestOps(SelectionKey.OP_ACCEPT);

        log.debug("register key : {}", openKey);

        open.bind(new InetSocketAddress(8080));

        while (true) {
           // selector方法,没有事件就阻塞,有事件继续运行
            // 事件未处理,不会阻塞,必须处理或者取消
            selector.select();

            // 处理事件,selectKey内部包含了所有发生的事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 需要删除selectionKey中的原色,防止下次处理会出现问题
                iterator.remove();
                log.debug("register key : {}", key);
                // 区分事件类型
                if (key.isAcceptable()) {
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    ByteBuffer buffer = ByteBuffer.allocate(16); //attachment
                    // 将bytebuffer作为附件关联
                    SelectionKey scKey = sc.register(selector, 0, buffer);
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.debug("{}", sc);
                } else if (key.isReadable()) {
                    // 触发事件的channel
                    try {
                        SocketChannel channel = (SocketChannel) key.channel();
                        // 获取关联的附件
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        int read = channel.read(buffer);// 正常断开的返回值是-1
                        if (read == -1) {
                            key.cancel();
                        } else {
                            split(buffer);
                            if (buffer.position() == buffer.limit()) {
                                ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity() * 2);
                                buffer.flip();
                                newBuffer.put(buffer);
                                key.attach(newBuffer);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();
                    }
                }
            }
        }
    }

ByteBuffer大小分配

  • ByteBuffer不是线程安全,需要每个channel维护一个独立的buffer
  • Bytebuffer不能太大,需要设计可变的buffer
    • 第一种方式:首先分配一个较小的buffer,每次数据不够进行扩容,优点保证消息连续,缺点是拷贝耗费性能
    • 第二种方式,多个数组组成buffer,多出来的内容写入数组,优点避免拷贝的性能损耗,缺点消息不连续,解析复杂

多线程优化

主线程建立连接,子线程处理读写事件

public class MultiThreadServer {
    public static void main(String[] args) throws IOException {
        Thread.currentThread().setName("boss");
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        Selector boss = Selector.open();
        SelectionKey bossKey = ssc.register(boss, 0, null);

        bossKey.interestOps(SelectionKey.OP_ACCEPT);

        ssc.bind(new InetSocketAddress(8080));

        while (true) {
            boss.select();
            Iterator<SelectionKey> iterator = boss.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();

                if (key.isAcceptable()) {
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                }
            }
        }
    }

    class Worker implements Runnable{
        private Thread thread;
        private Selector worker;
        private String name;
        private volatile boolean start = false;

        public Worker(String name) {
            this.name = name;
        }

        // 初始化线程selector
        public void register() throws IOException {
            if (!start) {
                thread = new Thread(this, name);
                thread.start();
                worker = Selector.open();
                start = true;
            }
        }

        @Override
        public void run() {
            while (true) {
                try {
                    worker.select();
                    Iterator<SelectionKey> iterator = worker.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        if (key.isReadable()) {
                            ByteBuffer buffer = ByteBuffer.allocate(16);
                            SocketChannel channel = (SocketChannel) key.channel();
                            channel.read(buffer);
                            buffer.flip();
                        }
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

启动之后,因为register和selector先后顺序导致阻塞

解决方法:

  • 使用线程安全队列传递数据,其他线程进行唤醒操作
  • 先唤醒,然后register
public class MultiThreadServer {
    public static void main(String[] args) throws IOException {
        Thread.currentThread().setName("boss");
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        Selector boss = Selector.open();
        SelectionKey bossKey = ssc.register(boss, 0, null);

        bossKey.interestOps(SelectionKey.OP_ACCEPT);

        ssc.bind(new InetSocketAddress(8080));

        // 创建worker,并进行注册
        Worker worker = new Worker("work-0");

        while (true) {
            boss.select();
            Iterator<SelectionKey> iterator = boss.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();

                if (key.isAcceptable()) {
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    log.debug("connected... {}", sc.getRemoteAddress());

                    log.debug("before register... {}", sc.getRemoteAddress());
                    worker.register(sc);// boss线程调用
                    // 关联selector

                    log.debug("after register...  {}", sc.getRemoteAddress());
                }
            }
        }
    }

    static class Worker implements Runnable{
        private Thread thread;
        private Selector worker;
        private String name;
        private volatile boolean start = false;
        // 线程传递数据
        private ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();

        public Worker(String name) {
            this.name = name;
        }

        // 初始化线程selector
        public void register(SocketChannel sc) throws IOException {
            if (!start) {
                worker = Selector.open();
                thread = new Thread(this, name);
                thread.start();
                start = true;
            }
            // 队列添加任务,并没有执行
            1.
            queue.add(() -> {
                try {
                    sc.register(worker, SelectionKey.OP_READ, null); // boss
                } catch (ClosedChannelException e) {
                    e.printStackTrace();
                }
            });
            worker.wakeup();
            
            2.
            worker.wakeup();
             sc.register(worker, SelectionKey.OP_READ, null); // boss
        }

        @Override
        public void run() {
            while (true) {
                try {
                    worker.select(); // 当前阻塞,wakeup进行唤醒
                    Runnable task = queue.poll();
                    if (task != null) {
                        task.run();
                    }
                    Iterator<SelectionKey> iterator = worker.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        if (key.isReadable()) {
                            ByteBuffer buffer = ByteBuffer.allocate(16);
                            SocketChannel channel = (SocketChannel) key.channel();
                            log.debug("read.. {}", channel.getRemoteAddress());
                            channel.read(buffer);
                            buffer.flip();
                        }
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

问题

Runtime.getRuntime().availableProcessors()在docker中获取到的是物理机的核心数,JDK10解决(使用jvm中的UseContainerSupport配置)

NIO和BIO

stream和channel
  • stream不会自动缓冲数据,channel会利用系统提供的发送缓冲区,接收缓冲区
  • stream只支持阻塞API,channel同时支持阻塞,非阻塞,配合selector实现多路复用
  • 都支持读写
IO模型

用户空间和内核空间的切换

阻塞IO:用户线程被阻塞,等待返回

非阻塞IO:用户线程没有被阻塞,有数据的情况下还是阻塞(等待数据非阻塞,复制数据阻塞)

多路复用:使用selector,等待数据阻塞(select),复制数据阻塞(read)

异步IO:线程自己不去获取结果,由其他线程送结果

同步IO:线程自己获取结果

零拷贝

读写操作:用户态和内核态切换3次,数据拷贝4次

NIO优化

使用DireByteBuffer

  • ByteBuffer.allocate(10) HeapByteBuffer使用java内存
  • ByteBuffer.allocateDirect(10) DireByteBuffer使用操作系统内存

切换2次

sendFile()方法—transferTo/transferFrom拷贝数据(无需切换到用户态)

切换1次

后续优化直接使用DMA刷进去,减少一次切换

AIO

解决数据复制阶段的阻塞问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值