netty学习系列四:读操作

一、缓存空间分配器:ByteBufAllocator

ByteBufAllocator接口为ByteBuf分配器,用于分配新的ByteBuf存储IO数据。

ByteBufAllocator类关系图

1、ByteBufAllocator

接口定义了 ByteBuf ioBuffer(int initialCapacity) 方法,用于分配一个ByteBuf

2、AbstractByteBufAllocator

实现了ByteBuf ioBuffer(int initialCapacity)方法,根据系统配置实际调用自己的抽象方法

ByteBuf newDirectBuffer(int initialCapacity,int maxCapacity);
ByteBuf newHeapBuffer(int initialCapacity,int maxCapacity);

其子类UnpooledByteBufAllocator和PooledByteBufAllocator实现了上述抽象方法。

3、UnpooledByteBufAllocator

非池化的ByteBuf分配器,具体实现了抽象方法。
1)对于newDirectBuffer
根据平台是否支持Unsafe方式,将实例化出一个UnpooledDirectByteBuf/UnpooledUnsafeDirectByteBuf。
UnpooledDirectByteBuf是一个基于NIO的Buffer,其内部持有一个NIO ByteBuffer buffer,并通过ByteBuffer.allocateDirect(initcapacity)方法进行实例化。
2)对于newHeapBuffer
根据平台是否支持Unsafe方式,将实例化出一个UnpooledHeapByteBuf/UnpooledUnsafeHeapByteBuf。
UnpooledHeapByteBuf是一个基于java heap的Buffer,其内部直接在java heap中申请byte[] array空间进行IO数据存储。

二、接收缓存分配器:RecvByteBufAllocator

1、接口概述

RecvByteBufAllocator接口用于分配一块大小合理的buffer空间,存储Channel读入的IO数据。具体功能交由内部接口Handle定义。

    interface Handle {
        /**
         * Creates a new receive buffer whose capacity is probably large enough to read all inbound data and small
         * enough not to waste its space.
         */
        ByteBuf allocate(ByteBufAllocator alloc);

        /**
         * Increment the number of messages that have been read for the current read loop.
         * @param numMessages The amount to increment by.
         */
        void incMessagesRead(int numMessages);

        /**
         * Set the bytes that have been read for the last read operation.
         * This may be used to increment the number of bytes that have been read.
         * @param bytes The number of bytes from the previous read operation. This may be negative if an read error
         * occurs. If a negative value is seen it is expected to be return on the next call to
         * {@link #lastBytesRead()}. A negative value will signal a termination condition enforced externally
         * to this class and is not required to be enforced in {@link #continueReading()}.
         */
        void lastBytesRead(int bytes);

        /**
         * Determine if the current read loop should should continue.
         * @return {@code true} if the read loop should continue reading. {@code false} if the read loop is complete.
         */
        boolean continueReading();

        /**
         * The read has completed.
         */
        void readComplete();
    }

allocate(ByteBufAllocator alloc)方法用于创建存放读入IO数据的ByteBuf。
readComplete()在读操作完成后调用,在实现类HandleImpl中执行record(int actualReadBytes)做了调整分配空间大小的逻辑。

2、实现类AdaptiveRecvByteBufAllocator

接口RecvByteBufAllocator的实现类,能根据前一次实际读取的字节数量,自适应调整当前缓存分配的大小。

三、NioEventLoop处理OP_READ事件

1、代码入口

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    //上面省略...
    if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
        unsafe.read();
        if (!ch.isOpen()) {
            // Connection already closed - no need to handle write.
            return;
        }
    }
    //下面省略...
}

NioEventLoop在处理其selector监听到的OP_READ事件时,会执行上面的代码逻辑,将OP_READ事件最终交由Unsafe处理,即执行NioByteUnsafe.read()方法。

        @Override
        public final void read() {
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final ByteBufAllocator allocator = config.getAllocator();
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    byteBuf = allocHandle.allocate(allocator);
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        break;
                    }

                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());

                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }

2、执行逻辑

1)获取缓存分配器

final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();

循环执行以下逻辑直到跳出--->
2)分配缓存ByteBuf

ByteBuf byteBuf = allocHandle.allocate(allocator);

3)将数据从nio.SocketChannel读取到byteBuf中

doReadBytes(byteBuf)

实际调用了NioSocketChannel.doReadBytes(ByteBuf byteBuf)方法。
进一步调用ByteBuf.writeBytes(ScatteringByteChannel in, int length)方法。
最终底层调用nio的ReadableByteChannel.read(ByteBuffer dst)方法。

                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        break;
                    }

若本次读取到的数据长度==0,表示本次OP_READ事件的数据已读取完毕,退出循环。
若本次读取到的数据长度<0,表示对端已断开socket连接,退出循环,执行NioByteUnsafe.closeOnRead()方法关闭Channel,关闭Channel的过程中执行了pipeline.fireChannelInactive()pipeline.fireChannelUnregistered()
4)触发ChannelRead事件,将读ByteBuf交给pipeline流转

pipeline.fireChannelRead(byteBuf);
byteBuf = null;

<----循环结束。结束条件:a、localReadAmount == 0或-1,b、循环读取到ByteBuf个数超过指定阈值

5)根据本次已读字节数,调整RecvByteBufAllocator的下次分配的缓存大小

allocHandle.readComplete();

6)触发ChannelReadComplete事件

pipeline.fireChannelReadComplete();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容