netty源码浅析-服务端数据接入

服务端数据接入

//读事件和连接事件就绪,执行unsafe.read()
            //如果是boss线程则是OP_ACCEPT事件,如果是work线程则是OP_READ事件
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }

和上面的客户端接入类似,我们之前已经分析过了在ionetty.channelnio.NioEventLoop#processSelectedKey()中work线程主要处理客户端的的read和write事件,这里是work线程,我们跟进到unsaferead()方法,由于当前是work线程,所以会走到io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read方法。

public final void read() {
            //channel配置类对象
            final ChannelConfig config = config();
            //pipeline
            final ChannelPipeline pipeline = pipeline();
            //byteBuf分配器
            final ByteBufAllocator allocator = config.getAllocator();
            //AdaptiveRecvByteBufAllocator内部HandleImpl
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            //设置初始值
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    //创建一个默认大小1024的直接内存
                    byteBuf = allocHandle.allocate(allocator);
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    //如果最后一次没有读取到数据说明读取数据完毕
                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        //释放内存
                        byteBuf.release();
                        byteBuf = null;
                        //那什么时候doReadBytes(byteBuf)的值小于零呢
                        //1.客户端发送数据完毕并关闭连接
                        //2.客户端关闭了连接,发生异常返回-1
                        close = allocHandle.lastBytesRead() < 0;
                        //读取完毕
                        if (close) {
                            readPending = false;
                        }
                        break;
                    }
                    //记录读取的次数,最大默认为16次
                    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 {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }

我们将上面代码拆开来看

 //channel配置类对象
final ChannelConfig config = config();
//pipeline
final ChannelPipeline pipeline = pipeline();
//byteBuf分配器
final ByteBufAllocator allocator = config.getAllocator();
//AdaptiveRecvByteBufAllocator内部HandleImpl
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
//设置初始值
allocHandle.reset(config);

可以看到首先获取ChannelConfig,这个config就是我们在创建NioServerSocketChannel时创建的 NioServerSocketChannelConfig,pipeline也是创建一起创建时创建的

 public NioServerSocketChannel(ServerSocketChannel channel) {
        //1.调用父类构造方法
        super(null, channel, SelectionKey.OP_ACCEPT);
        //2.创建配置类
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

我们看下allocator的创建,跟踪一下config.getAllocator方法,来到了

public ByteBufAllocator getAllocator() {
        return allocator;
    }
private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;

allocator就是DefaultChannelConfig的属性,也就是在创建DefaultChannelConfig时就创建了,我们继续跟踪进入到ByteBufAllocator接口中,继续跟踪到ByteBufUtil中

ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;
static {
        //判断当前运行环境是不是安卓环境,如果是那就是非池化的
        String allocType = SystemPropertyUtil.get(
                "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
        allocType = allocType.toLowerCase(Locale.US).trim();

        ByteBufAllocator alloc;
        //默认创建的是池化内存
        if ("unpooled".equals(allocType)) {
            alloc = UnpooledByteBufAllocator.DEFAULT;
            logger.debug("-Dio.netty.allocator.type: {}", allocType);
        } else if ("pooled".equals(allocType)) {
            alloc = PooledByteBufAllocator.DEFAULT;
            logger.debug("-Dio.netty.allocator.type: {}", allocType);
        } else {
            alloc = PooledByteBufAllocator.DEFAULT;
            logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
        }

        DEFAULT_ALLOCATOR = alloc;

        THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalDirectBufferSize", 64 * 1024);
        logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);

        MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.maxThreadLocalCharBufferSize", 16 * 1024);
        logger.debug("-Dio.netty.maxThreadLocalCharBufferSize: {}", MAX_CHAR_BUFFER_SIZE);
    }

这里会判断当前运行的环境是不是安卓环境,如果是就创建非池化的内存,默认是创建池化的内存,关于池化非池化我们之后会在分析,如果平台支持直接内存,会申请直接内存。这点我们只需要知道起始就是申请了一块内存。我们继续回到io.netty.channelnio.AbstractNioByteChannelNioByteUnsafe#read方法,这里和上面分析的客户端连接接入相同,调用ionettychannelAdaptiveRecvByteBufAllocator#newHandle方法创建了内部类 Handlelmpl对象。并调用reset方法对一些属性设置初始值。

public void reset(ChannelConfig config) {
            this.config = config;
            maxMessagePerRead = maxMessagesPerRead();//16
            totalMessages = totalBytesRead = 0;
        }

下面我们继续分析read方法

ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    //创建一个默认大小1024的直接内存
                    byteBuf = allocHandle.allocate(allocator);
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    //如果最后一次没有读取到数据说明读取数据完毕
                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        //释放内存
                        byteBuf.release();
                        byteBuf = null;
                        //那什么时候doReadBytes(byteBuf)的值小于零呢
                        //1.客户端发送数据完毕并关闭连接
                        //2.客户端关闭了连接,发生异常返回-1
                        close = allocHandle.lastBytesRead() < 0;
                        //读取完毕
                        if (close) {
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }
                    //记录读取的次数,最大默认为16次
                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());

首先会根据上面创建的allocator创建一块内存大小默认为1024的直接内存,然后调用doReadBytes开始读取数据写入内存,我们跟踪一下doReadBytes方法

protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();//handleImpl
        //byteBuf中可以读取的内容大小
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());
        //读取数据到byteBuf
        return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
    }

这里将数据读取到我们上面申请的内存中,然后返回去读的字节数

public void lastBytesRead(int bytes) {
            lastBytesRead = bytes;//最后一次读取的字节长度
            if (bytes > 0) {
                //一共读取的字节长度
                totalBytesRead += bytes;
            }
        }

如果读取方法返回值为0说明数据读取尸经结束如果返回为-1说明客户端关闭了连接,则将内存释放,并跳出循环。然后记录读取到的message的数量,并将读取到的数据在pipeline中传递。我们看下跳出循环的条件是什么

public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) {
            //config.isAutoRead()默认为true
            return config.isAutoRead() &&
                    //respectMaybeMoreData默认为true
                   (!respectMaybeMoreData || maybeMoreDataSupplier.get()) &&
                    //已经读取到的总连接数是不是超过了maxMessagePerRead,默认是16
                   totalMessages < maxMessagePerRead &&
                    //totalBytesRead设置为0,一直为false
                   totalBytesRead > 0;
        }

我们分开来看这 几个条件

  • config.isAutoRead():默认为true
  • (lrespectMaybeMoreDatamaybeMoreDataSupplierget():respectMaybeMoreData默认为true,这里主要关注的是maybeMoreDataSupplierget()的返回值,我们看一下起始就是比较attemptedBytesRead== lastBvtesRead是否相等attemptedBytesRead就是我们在读取的时候设置的值,起始就是bytebuf可以读取的字节数量,lastBytesRead是每次去读的自己字节数,也就是如果他们俩相等,则说明我们申请的bytebuf直接被读满了,也就是还有数据没有读完,如果不相等,则申请的内存足够读取数据,数据已经读取完毕。
  • totalMessages<maxMessagePerRead:判断读取的总的message数量不能超过最大值,默认为16个,这里最多16次可能是为了不想延迟太久,其他的channel传输的数据没有办法得到处理。
  • totalBytesRead>0:如果读取到数据则totalBytesRead的值总是大于0
    这里可以看到会判断我们申请的内存是不是被读取满了,如果是则可能数据还没有读取完毕,则再执行一次循环重新申请内存在读取一次,并且读取的message的数量不能超过16次。我们继续分析
allocHandle.readComplete();
pipeline.fireChannelReadComplete();

if (close) {
    closeOnRead(pipeline);
}

我们跟踪readComplete方法进入io.netty.channel.AdaptiveRecvByteBufAllocator.HandleImpl#readComplete方法

 public void readComplete() {
            record(totalBytesRead());
        }
private void record(int actualReadBytes) {
            //判断我们读取的字节数和SIZE_TABLE数组中index对应位置的前一个位置的大小进行比较
            //如果比这个值小说明需要缩容
            if (actualReadBytes <= SIZE_TABLE[max(0, index - INDEX_DECREMENT - 1)]) {
                if (decreaseNow) {
                    index = max(index - INDEX_DECREMENT, minIndex);
                    //获取缩容后的大小
                    nextReceiveBufferSize = SIZE_TABLE[index];
                    decreaseNow = false;
                } else {
                    //将缩容标志位设置未true,等待下次进行扩展
                    decreaseNow = true;
                }
                //如果读取的数据大小大于分配值则需要扩容
            } else if (actualReadBytes >= nextReceiveBufferSize) {
                index = min(index + INDEX_INCREMENT, maxIndex);
                nextReceiveBufferSize = SIZE_TABLE[index];
                decreaseNow = false;
            }
        }

这里我们知道AdaptiveRecvByteBufAllocator是 个弹性的内存分配 内部维护了分配内存大小的数组

static {
        List<Integer> sizeTable = new ArrayList<Integer>();
        //如果内存大小小于512则按照每次递增16
        for (int i = 16; i < 512; i += 16) {
            sizeTable.add(i);
        }
        //如果大于512每次扩展2倍
        for (int i = 512; i > 0; i <<= 1) {
            sizeTable.add(i);
        }

        SIZE_TABLE = new int[sizeTable.size()];
        for (int i = 0; i < SIZE_TABLE.length; i ++) {
            SIZE_TABLE[i] = sizeTable.get(i);
        }
    }

它会根据本次接收的文件大小来调整下次申请内存的大小,缩容或者是扩容,尽量可以刚好满足内存大小的分配然后触发pipelinefireChannelReadComplete(),传递ReadComplete事件。最后如果发送数据过程中,客户端发送异常关闭了这个连接,则执行一些关闭channel的操作。到这里我们分析完了服务端接收数据的过程。如果有分析错误的地方还请不吝指正。感谢!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值