一、Netty学习前置NIO

NIO翻译

non-blocking IO 非阻塞IO
new IO 新的IO

NIO三大组件

channel 数据传输通道

常用channel
java.nio.channels.FileChannel
java.nio.channels.SocketChannel

buffer 缓冲区

常用buffer
java.nio.ByteBuffer
java.nio.HeapByteBuffer
java.nio.DirectByteBuffer

使用channel和ByteBuffer读取文件
    @Test
    public void readAllFile() {
        // 获取文件流
        try (FileChannel channel = new FileInputStream("a.txt").getChannel()) {
            // 设置缓冲区
//            java堆内存,受GC影响 java.nio.HeapByteBuffer
//            ByteBuffer buffer = ByteBuffer.allocate(10);
//            直接内存,读写效率高,减少一次copy,分配效率低 java.nio.DirectByteBuffer
            ByteBuffer buffer = ByteBuffer.allocateDirect(10);
            while (true) {
                // 从channel读取数据,写入到buffer中,使用read或者put
                int len = channel.read(buffer);
                if (len == -1) {
                    break;
                }
                // 切换至读模式
                buffer.flip();
                while (buffer.hasRemaining()) {
//                    或者使用channel.write读取数据,get后position后移可以使用rewind将position置0
                    byte b = buffer.get();
                    byte[] byteArray = {b};
                    log.info(new String(byteArray, StandardCharsets.UTF_8));
                }
                // 清空buffer
                buffer.clear();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
ByteBuffer常用属性
  1. 容量capacity
  2. 写入位置Position
  3. 限制limit
    mark(设置标记)reset(将position重置到mark位置)hasRemaining(是否有剩余的空间)
黏包处理案例
    @Test
    public void stickyPacket(){
        ByteBuffer source = ByteBuffer.allocate(60);
        source.put("微信小程序\n马上行计划管理\n欢".getBytes(StandardCharsets.UTF_8));
        reduce(source);
        source.put("迎体验\n".getBytes(StandardCharsets.UTF_8));
        reduce(source);
    }
    public static void reduce(ByteBuffer source){
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            if (source.get(i) == '\n') {
                int length = i+1-source.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(source.get());
                }
                target.flip();
                log.info(StandardCharsets.UTF_8.decode(target).toString());
            }
        }
        source.compact();
    }

selector

java.nio.channels.Selector管理channel。
selector事件发生时机

  1. 客户端发起请求时,触发accept事件
  2. 客户端发送数据进入服务器,客户端正常异常关闭时,都会触发read事件,发送数据大于buffer缓冲区,会触发多次读取事件。
  3. channel可写,会触发write事件
  4. linux下niobug发生时
    使用selector实现简单连接写入读取。
// 服务端
    @Test
    public void noBlockServer() throws IOException {
        // 创建选择器
        Selector selector = Selector.open();
        // 创建服务器
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 绑定端口
        ssc.bind(new InetSocketAddress(8089));
        ssc.configureBlocking(false);
        // 注册到选择器
        SelectionKey sscKey = ssc.register(selector, 0, null);
        // 关注事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while(true){
            // 没有事件就阻塞,有未处理的事件时不会阻塞
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()){
                    ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                    // 连接
                    SocketChannel accept = channel.accept();
                    // 设置非阻塞
                    accept.configureBlocking(false);
                    // 注册,同时添加缓存每个通道都使用自己单独的缓存
                    // 读和写的attach需要区分
                    ChannelAttachment channelAttachment = new ChannelAttachment();
                    ByteBuffer buffer = ByteBuffer.allocate(8);
                    channelAttachment.setReadBuffer(buffer);
                    SelectionKey acceptKey = accept.register(selector, 0, channelAttachment);
                    // 只关注读事件
                    acceptKey.interestOps(SelectionKey.OP_READ);
                    // 向客户端发送信息
//                    StringBuilder stringBuilder = new StringBuilder();
//                    for (int i = 0; i < 19000; i++) {
//                        stringBuilder.append("欢迎使用马上行计划管理");
//                    }
                    ByteBuffer writeBuffer = StandardCharsets.UTF_8.encode("欢迎使用马上行计划管理");
                    int write = accept.write(writeBuffer);
                    log.info("写入字节"+write);
                    // 如果没有发送完关注写事件
                    if (writeBuffer.hasRemaining()){
                        acceptKey.interestOps(acceptKey.interestOps()+SelectionKey.OP_WRITE);
                        ChannelAttachment attachment = (ChannelAttachment) acceptKey.attachment();
                        attachment.setWriteBuffer(writeBuffer);
//                        acceptKey.attach(attachment);
                    }
                }else if (key.isReadable()){
                    try {
                        SocketChannel readChannel = (SocketChannel)key.channel();
                        ChannelAttachment attachment = (ChannelAttachment) key.attachment();
                        ByteBuffer buffer = attachment.getReadBuffer();
                        int read = readChannel.read(buffer);
                        if (read>0){
                            reduce(buffer);
                            // buffer不足自动扩容
                            if (buffer.position()==buffer.limit()){
                                ByteBuffer newByteBuffer = ByteBuffer.allocate(buffer.capacity()*2);
                                buffer.flip();
                                newByteBuffer.put(buffer);
                                key.attach(newByteBuffer);
                            }
                        }else if (read==-1){
                            log.info("自动断开连接");
                            key.cancel();
                        }
                    }catch (IOException e){
                        // 移除断开的key
                        key.cancel();
                        log.error(e.getMessage(),e);
                    }
                }else if (key.isWritable()){
                    ChannelAttachment attachmentChannel = (ChannelAttachment) key.attachment();
                    ByteBuffer attachment = attachmentChannel.getWriteBuffer();
                    SocketChannel channel = (SocketChannel)key.channel();
                    int write = channel.write(attachment);
                    log.info("写入字节"+write);
                    if (!attachment.hasRemaining()) {
                        key.interestOps(key.interestOps()-SelectionKey.OP_WRITE);
                        attachmentChannel.setWriteBuffer(null);
                        key.attach(attachmentChannel);
                    }
                }

            }
        }
    }

    public static void reduce(ByteBuffer source){
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            if (source.get(i) == '\n') {
                int length = i+1-source.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(source.get());
                }
                target.flip();
                log.info(StandardCharsets.UTF_8.decode(target).toString());
            }
        }
        source.compact();
    }
    @Data
    class ChannelAttachment {
        private ByteBuffer readBuffer;
        private ByteBuffer writeBuffer;
    }
// 客户端读取
    @Test
    public void blockClientReceive() throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost",8089));
        log.info("等待连接");
        int count = 0;
        while(true){
            ByteBuffer buffer = ByteBuffer.allocate(8);
            count += sc.read(buffer);
            buffer.clear();
            log.info(count+"");
        }
    }
// 客户端写入
    @Test
    public void blockClient() throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost",8089));
        log.info("等待连接");
        sc.write( StandardCharsets.UTF_8.encode("马上行计划管理2,马上行计划管理3,马上行计划管理4,马上行计划管理5\n"));
        log.info("success");
        sc.close();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值