【Netty】netty接收、读、写整体过程源码解析

整体流程分析

上一篇文章,通过源码分析了netty的启动过程,整体上来说,其实就是通过创建boss\worker线程池,然后注册channel,以及绑定端口等,然后就开始轮训的方式,接受客户端的请求。而请求中主要包含 连接、读、写 三种不同的事件,本篇我们主要围绕这三种事件进行源码分析。

        EventLoop eventLoop = new NioEventLoopGroup().next();
        eventLoop.execute(() -> {
            System.out.println("hello workd");
        });

接收请求-accepct过程

判断当前线程是不是NIO线程,如果不是的启动一个NIO线程

private void execute(Runnable task, boolean immediate) {
        // 当前线程是否是NIO 线程
        boolean inEventLoop = inEventLoop();
        // 添加到任务队列中
        addTask(task);
        // 是否是nio线程 ,不是的话
        if (!inEventLoop) {
            // 开启一个线程
            startThread();
        } 
    }

启动一个线程。先CAS修改启动状态。使用CAS其实可以提升整体的并发度。可以看到如果线程执行启动失败的话,会采用finally进行CAS修改成原来的状态

    // NIO 线程 只会启动一次,启动成功后 直接从taskQueue 获取数据
    private void startThread() {
        // state = ST_NOT_STARTED; 初始值
        if (state == ST_NOT_STARTED) {
            // CAS 状态 更新 not_started -> st_started 代表线程启动
            if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
                // 初始化值为false
                boolean success = false;
                try {
                    // 做真正的NIO启动
                    doStartThread();
                    success = true;
                } finally {
                    if (!success) {
                        STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                    }
                }
            }
        }
    }

doStartThread(); 利用线程池创建一个任务。

// 只会创建一次
    private void doStartThread() {
        assert thread == null;
        // 如下就是NIO线程
        executor.execute(new Runnable() {
            @Override
            public void run() {
                thread = Thread.currentThread();
                 // 核心 ⭐️
                 SingleThreadEventExecutor.this.run();
                 success = true;
           

这个run方法 ,其实就是select(), 轮训查询事件。这里包括IO任务 普通任务,定时任务等。

// io任务 普通任务 定时任务 网络通信
    // eventLoop
    @Override
    protected void run() {
        int selectCnt = 0;
        for (;;) {
            try {
                int strategy;
                try {
                    // hasTasks() 普通任务 或者 定时任务
                    // 1.如果存在普通任务 selectNow 不阻塞
                    // 如果没有普通任务就阻塞
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    case SelectStrategy.CONTINUE:
                        continue;

                    case SelectStrategy.BUSY_WAIT:
                        // fall-through to SELECT since the busy-wait is not supported with NIO

                    case SelectStrategy.SELECT:
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            if (!hasTasks()) {
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            // This update is just to help block unnecessary selector wakeups
                            // so use of lazySet is ok (no race condition)
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                        // fall through
                    default:
                    }
                } 

                selectCnt++;
                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
                // 执行IO
                if (ioRatio == 100) {
                    try {
                        if (strategy > 0) {
                            // 如何执行IO任务
                            processSelectedKeys();
                        }
                    } finally {
                        // 执行普通任务
                        // Ensure we always run tasks.
                        ranTasks = runAllTasks();
                    }
                } else if (strategy > 0) {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else {
                    // 没有IO任务,执行其他所有的任务
                    ranTasks = runAllTasks(0); // This will run the minimum number of tasks
                }
            } 
        }
    }

由于我们的关注点在于客户端和服务端IO事件的交互上,所以只关系 accecpt\read\write 事件

    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        // 校验处理 检查该selectionKey是否有效,如果无效,则关闭channel
        if (!k.isValid()) {}
//        该方法主要是对SelectionKey k进行了检查,有如下几种不同的情况
//
//        1)OP_ACCEPT,接受客户端连接
//
//        2)OP_READ, 可读事件, 即 Channel 中收到了新数据可供上层读取。
//
//        3)OP_WRITE, 可写事件, 即上层可以向 Channel 写入数据。
//
//        4)OP_CONNECT, 连接建立事件, 即 TCP 连接已经建立, Channel 处于 active 状态。
        // 不同事件 处理不同功能
        try {
            int readyOps = k.readyOps();
            // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
            // the NIO JDK channel implementation may throw a NotYetConnectedException.
            // 连接
            // 需要移除OP_CONNECT 否则可能出现理会返回不会有任何阻塞
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
                // See https://github.com/netty/netty/issues/924
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);

                unsafe.finishConnect();
            }

            // 核心事件 判断 以及处理
            // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
            // 将缓冲区中的数据发送出去,如果缓冲区数据都发送完成,清除之前关注的OP_WRITE标记
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
               unsafe.forceFlush();
            }

            // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
            // to a spin loop
            // 读 或者 接收
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }
        public void read() {
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {// ⭐️ 创建一个SocketChannel对象
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (continueReading(allocHandle));
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                // 遍历处理
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    // 消费过程
                    // readBuf.get(i) 获取nioSocketChannel
                    // 触发pipiline的Read功能
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                readBuf.clear();
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }
            } 
        }
    }

1.创建原生的socketChannel, 这里会调用父类设置一些基础的信息,并且设置当前为一个readChannel

    protected int doReadMessages(List<Object> buf) throws Exception {
        // 创建原生的socketChannel
        // 这里其实要是看是否是阻塞的.
        SocketChannel ch = SocketUtils.accept(javaChannel());

        try {
            if (ch != null) {
                // 创建了NIOScoektChannel 将SocketChannel 存储到NIOSocketChannel
                // 将原始的sovketchannel 设置为非阻塞
                // ServerSOcketChannel设置父类
                // 添加到list中
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }
        } catch (Throwable t) {
            logger.warn("Failed to create a new channel from an accepted socket.", t);

            try {
                ch.close();
            } catch (Throwable t2) {
                logger.warn("Failed to close a socket.", t2);
            }
        }

        return 0;
    }

由于在初始化服务端的时候,往pipeline中添加一个ServerBootstrapAcceptor对象,当出发accpect事件,就会调用read方法。

 p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                // 这是一个接入器,专门接受新请求,把新的请求扔给某个事件循环器
                // 看到这里,我们发现其实init只是初始化了一些基本的配置和属性,
                // 以及在pipeline上加入了一个接入器,用来专门接受新连接,并没有启动服务.
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs,
                                extensions));
                    }
                });
            }
        });

调用read方法就会注册一个read NIO线程,这个线程是worker线程池的线程。用来处理read事件。

	public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            setChannelOptions(child, childOptions, logger);
            setAttributes(child, childAttrs);

            if (!extensions.isEmpty()) {
                for (ChannelInitializerExtension extension : extensions) {
                    try {
                        extension.postInitializeServerChildChannel(child);
                    } catch (Exception e) {
                        logger.warn("Exception thrown from postInitializeServerChildChannel", e);
                    }
                }
            }

            try {
            // 核心注册方法
                childGroup.register(child).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            forceClose(child, future.cause());
                        }
                    }
                });
            } catch (Throwable t) {
                forceClose(child, t);
            }
        }

在这里插入图片描述

worker线程-read过程

       public final void read() {
            // 1.获取配置信息
            final ChannelConfig config = config();
            if (shouldBreakReadReady(config)) {
                clearReadPending();
                return;
            }
            // 2.交给pipeline
            final ChannelPipeline pipeline = pipeline();
            // 创建分配器
            final ByteBufAllocator allocator = config.getAllocator();
            // 服务端接收数据的大小
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                // 循环的原因
                // bytebuf 大小可能不够 需要循环处理
                do {
                    // 接收数据 自适应  相对于ByteBuf
                    //0.自适应 如果数据量大,扩容 如果数据量小 缩小
                    //1.决定ByteBuf 用的是直接内存 还是堆内存
                    byteBuf = allocHandle.allocate(allocator);
                    //doReadBytes 通过时机IO读区数据
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {// 读区完毕
                        // nothing was read. release the buffer.
                        byteBuf.release(); // 释放
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) { // 关闭
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }

                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());
                // do while 是判断当前是否读区完数据
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
            }
        }
    }

这里涉及到几个点那就是在读区数据的时候。

  • 堆内存和直接内存
  • 自动扩缩容的处理(根据读区缓冲区大小 是否读区完数据调整)

worker线程-write过程

        protected void flush0() {
            // 状态判断
            if (inFlush0) {
                // Avoid re-entrance
                return;
            }

            final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null || outboundBuffer.isEmpty()) {
                return;
            }

            inFlush0 = true;

            // Mark all pending write requests as failure if the channel is inactive.
            //
            if (!isActive()) {
                try {
                    // Check if we need to generate the exception at all.
                    if (!outboundBuffer.isEmpty()) {
                        if (isOpen()) {
                            outboundBuffer.failFlushed(new NotYetConnectedException(), true);
                        } else {
                            // Do not trigger channelWritabilityChanged because the channel is closed already.
                            outboundBuffer.failFlushed(newClosedChannelException(initialCloseCause, "flush0()"), false);
                        }
                    }
                } finally {
                    inFlush0 = false;
                }
                return;
            }

            try {
                // 核心 ⭐️
                doWrite(outboundBuffer);
            } catch (Throwable t) {
                handleWriteError(t);
            } finally {
                inFlush0 = false;
            }
        }

在这里插入图片描述

总结

整体的过程代码还是比较多的,这里只是主要介绍了接收客户端请求的处理逻辑,对于读和写的具体代码没有展示。但是我们可以从一个宏观的角度去理解。
1.接收请求,其实就是boss线程开辟一个NIO线程处理客户端的连接,将获取到的连接,通过注册一个worker线程进行后续的读和写操作。
2.对于读操作,可以利用直接内存进行提升整体的读取数据的性能,减少数据拷贝的次数。以及进行动态的扩容实现数据的快速读取。
3.对于写操作,需要经历几个过程。数据是存储在outboundBuffer缓冲中的。
而对于直接内存来说,有两种选择,一种是池化的直接内存,另一种就是保存在线程中的ThreadLocalDirectBuffer 直接内存对象。
而数据会添加到链表中,经过addMessage() unflushEntry 状态到 addFlush的flushEntry状态。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qxlxi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值