Netty服务端请求接受过程源码剖析

news2024/11/17 16:37:01

在这里插入图片描述

目标

  • 服务器启动后,客户端进行连接,服务器端此时要接受客户端请求,并且返回给客户端想要的请求,下面我们的目标就是分析Netty 服务器端启动后是怎么接受到客户端请求的。
  • 我们的代码依然与上一篇中用同一个demo, 用io.netty.example下的echo包下的代码
  • 我们直接debug模式启动Server端,让后在浏览器输入Http://localhost:8007,接着以下代码分析
源码剖析
  • 在上一篇文章Netty启动过程源码分析中,我们知道了服务器最终注册 一个Accept事件等待客户端的连接,同时将NioServerSocketChannel注册到boss单例线程池中,也就是EventLoop如上图左边黄色区域部分
  • 因此我们想要分析接受client连接的代码,先找到对应的EventLoop源码,如上图中NioEventLoop 循环,找到如下源码
//代码位置 NioEventLoop --- >  run()
@Override
    protected void run() {
        int selectCnt = 0;
        for (;;) {
            try {
                int strategy;
                try {
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    .......
                        // 处理各种strategy类型
                    default:
                    }
                } catch (IOException e) {
                    // If we receive an IOException here its because the Selector is messed up. Let's rebuild
                    // the selector and retry. https://github.com/netty/netty/issues/8566
                    rebuildSelector0();
                    selectCnt = 0;
                    handleLoopException(e);
                    continue;
                }

                selectCnt++;
                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
                if (ioRatio == 100) {
                    try {
                        if (strategy > 0) {
                        //对strategy事件进行处理
                            processSelectedKeys();
                        }
                    } finally {
                        ranTasks = runAllTasks();
                    }
                } else if (strategy > 0) {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                      .......
                    }
                } else {
                    ranTasks = runAllTasks(0); // This will run the minimum number of tasks
                }

             .......
            } catch (CancelledKeyException e) {
              .......
            } catch (Throwable t) {
                handleLoopException(t);
            }
          .......
        }
    }
  • 如上代码中 strategy 更具请求的类型走不同的策略,最后处理策略的方法是 processSelectedKeys();,我们继续根核心方法 processSelectedKeys();,如下源码
//进入processSelectedKeys ---》processSelectedKeysOptimized(); ---〉processSelectedKey
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        if (!k.isValid()) {
            final EventLoop eventLoop;
            try {
                eventLoop = ch.eventLoop();
            } catch (Throwable ignored) {
                return;
            }
            if (eventLoop == this) {
                unsafe.close(unsafe.voidPromise());
            }
            return;
        }
        try {
            int readyOps = k.readyOps();
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);
                unsafe.finishConnect();
            }
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                ch.unsafe().forceFlush();
            }
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

  • 第一个if中最事件合法性验证,接着获取readyOps,我们debug得到是16,如下图

在这里插入图片描述

  • 找到SelectionKey中16 代码的意义
 /**
     * Operation-set bit for socket-accept operations.
     *
     * <p> Suppose that a selection key's interest set contains
     * <tt>OP_ACCEPT</tt> at the start of a <a
     * href="Selector.html#selop">selection operation</a>.  If the selector
     * detects that the corresponding server-socket channel is ready to accept
     * another connection, or has an error pending, then it will add
     * <tt>OP_ACCEPT</tt> to the key's ready set and add the key to its
     * selected-key&nbsp;set.  </p>
     */
    public static final int OP_ACCEPT = 1 << 4;
  • 术语连接请求,这就是我们拿到了之前用Http://localhost:8007 请求的连接,接着继续跟进代码 EventLoopGroup —> processSelectedKey —> unsafe.read(); 其中unsafe是NioMessageUnsafed,上一篇中有过分析用来处理消息接收
  • 继续跟进AbstractNioMessageChannel —> read() ,得到如下源码,删了一些对本次无关的一些代码,如下
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 {
                        int localRead = doReadMessages(readBuf);
                     ......
                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }
                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    pipeline.fireChannelRead(readBuf.get(i));
                }
              ......

                if (exception != null) {
                   ......
                }

                if (closed) {
                   ......
                }
            } finally {
               ......
            }
        }
    }

  • assert eventLoop().inEventLoop(); 判断改eventLoop线程是否当前线程

  • ChannelConfig config = config(); 获取NioServerSocketChannelConfig

  • ChannelPipeline pipeline = pipeline(); 获取DefaultChannelPipeline。他是一个双向链表,可以看到内部包含 LoggingHandler,ServerBootStraptHandler

  • 继续跟进 NioServersocketChannel —> doMessage(buf),可以进入到NioServerSocketChannel,找到doMessage方法

protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = SocketUtils.accept(javaChannel());
        try {
            if (ch != null) {
                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;
    }
  • 参数buf是一个静态队列。private final List readBuf = new ArrayList(); 读取boss线程中的NioServerSocketChannel接受到的请求,并且将请求放到buf容器中

  • SocketChannel ch = SocketUtils.accept(javaChannel()); 通过Nio中工具类的建立连接,其实底层是调用了ServerSocketChannelImpl —> accept()方法建立TCP连接,并返回一个Nio中的SocketChannel

  • buf.add(new NioSocketChannel(this, ch)); 将获取到的Nio中SocketCHannel包装成Netty中的NioSocketChannel 并且添加到buf队列中(list)

  • doReadMessages到这分析完。

  • 我们回到回到EventLoopGroup —> ProcessSelectedKey

  • 循环遍历之前doReadMessage中获取的buf中的所有请求,调用Pipeline的firstChannelRead方法,用于处理这些接受的请求或者其他事件,在read方法中,循环调用ServerSocket的Pipeline的fireChannelRead方法,开始执行管道中的handler的ChannelRead方法,如下

在这里插入图片描述

  • 继续跟进,进入 pipeline.fireChannelRead(readBuf.get(i)); 一直跟到AbstracChannelHandlerContext —> invokeChannelRead
private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }
  • 进入 handler() 中,DefaultChannelPipeline —> handler()
  • debug源码可以看到,在管道中添加了多个Handler,分别是:HeadContext,LoggingContext,ServerBootStrapAcceptor,TailContext 因此debug时候会依次进入每一个Handler中。我们重点看ServerBootStrapAcceptor中的channelRead方法
 @Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

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

            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);
            }
        }
  • 因为参数msg是NioSocketChannel,此处强制转成channel,
  • child.pipeline().addLast(childHandler); 将我们在main方法中设置的EchoServerHandler添加到pipeline的handler链表中
  • setChannelOptions 对TCP参数赋值
  • setAttributes 设置各种属性
  • childGroup.register(child).addListener(…) 将NioSocketChannel注册到 NioEventLoopGroup中的一个EventLoop中,并且添加一个监听器
  • 以上NioEventLoopGroup就是我们main方法创建的数组workerGroup
  • 进入register方法, MultithreadEventLoopGroup —>register , SingleThreadEventLoop —>register , AbstractChannel —> register,如下
  • 首先看MultithreadEventLoopGroup中的register
@Override
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }
  • 进入next()方法中,最终我们可以追到 DefaultEventExecutorChooserFactory — > PowerOfTwoEventExecutorChooser — > next() 内部类中的next
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
        private final AtomicInteger idx = new AtomicInteger();
        private final EventExecutor[] executors;

        PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
    }
  • 入上我们通过debug可以看到,next返回的就是我们在workerGroup中创建的线程数组中的某一个子线程EventExecutor
    在这里插入图片描述

  • 接下来我们在回到register方法: AbstractChannel —> register 方法,如下:

@Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ......
            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                 ......
                }
            }
        }
  • 关键方法register0
private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;

                // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                // user may already fire events through the pipeline in the ChannelFutureListener.
                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        // This channel was registered before and autoRead() is set. This means we need to begin read
                        // again so that we process inbound data.
                        //
                        // See https://github.com/netty/netty/issues/4805
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }
  • 进入 doRegister(); 方法:AbstractNioChannel —> doRegister
@Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }
  • 上代码,selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);此处我们将bossGroup中的EventLoop的channel 注册到workerGroup中的EventLoop中的 select中,方法中会得到一个selectionKey
  • 我们可以看register方法的注视,如下:
Registers this channel with the given selector, returning a selectionkey.
使用给定的选择器注册此通道,并返回选择键。
  • 接着debug,最终会到 AbstractNioChannel 中的doBeginRead方法

 @Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }
  • 此方法比较难进入,包含了几个异步,将之前的断电去掉,再次http请求,可以到这个方法中
  • 追到这里,针对客户的连接已经完成,接下来是读取监听事件,也就是bossGroup的连接建立,注册步骤已近完成了,接下来就是workerGroup中的事件处理了

Netty接收请求过程梳理

  • 总流程:接收连接 — 》创建一个新的NioSocketChannel —〉 注册到一个WorkerEventLoop上 —》 注册selecotRead事件

    • 服务器沦陷Accept事件(文中最开始的那个for循环),获取事件后调用unsafe的read方法,这个unsafe是ServerSocket的内部类,改方法内部由2部分组成
    • doReadMessage 用于创建NioSocketChannel对象,改对象包装JDK的NioChannel客户端,该方法创建一个ServerSocketChannel
    • 之后执行pipeline.firstChannelRead方法,并且将自己绑定到一个chooser选择器选择的workerGroup中的某个EventLoop上,并且注册一个0(连接),表示注册成功,但是并没有注册1 (读取)
  • 上一篇:Netty启动流程源码剖析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/353660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tektronix TAP3500/泰克TAP3500有源探头

产品概览 泰克 TAP3500 有源探头&#xff0c;2 .5 GHz 泰克 TAP3500 单端有源 FET 探头是一种多功能且易于使用的探头&#xff0c;可提供数字系统设计所需的高速电气和机械性能。泰克 TAP3500 探头专为使用和连接到 TekVPI™ 探头接口而设计。 泰克 TAP3500 有源探头的特性和规…

带你认识一下什么是函数式接口Comparator

函数式接口Comparator 1、函数式接口是什么&#xff1f; 所谓的函数式接口&#xff0c;实际上就是接口里面只能有一个抽象方法的接口。Comparator接口就是一个典型的函数式接口&#xff0c;它只有一个抽象方法compare。 有人会说equales方法也没有方法体&#xff0c;也是抽象…

江苏专转本考试倒计时,该如何自救?

专转本考试倒计时&#xff0c;该如何自救&#xff1f;第一点&#xff1a;回归考纲教材&#xff0c;刷题。 最后一段时间&#xff0c;一定要回归考纲及教材&#xff01;要把知识点看细&#xff0c;看明白。 另外大家也可以根据历年考试题对知识点进行系统地复习和梳理&#xff0…

【java】Spring Boot --深入SpringBoot注解原理及使用

步骤一 首先&#xff0c;先看SpringBoot的主配置类&#xff1a; SpringBootApplication public class StartEurekaApplication {public static void main(String[] args){SpringApplication.run(StartEurekaApplication.class, args);} }步骤二 点进SpringBootApplication来…

线程间通信的常用方式

线程间通信的常用方式 1.简介 线程通信简单来说就是实现线程的交替工作&#xff0c;传递信息。例如在一个方法中我有两个线程A和B在运行&#xff0c;我希望线程A先向一个集合里面循环新增数据&#xff0c;当增加到第五次的时候&#xff0c;线程B才开始执行其他的操作。 线程间…

博客系统测试用例

博客系统测试用例目录博客系统测试用例博客系统删除功能测试用例 (判定表)提交BUG 1测试用例 1测试用例 2提交BUG提交BUG 2博客系统测试用例 博客系统删除功能测试用例 (判定表) # 首先确定输入条件与输出条件 输入条件: 博客作者, 非博客作者, 点击删除博客输出条件: 删除成…

web移动端:rem适配布局(重点)

目录 1. rem基础 2.媒体查询 2.1 媒体查询的概念 2.2 语法规范 2.2.1 2.2.2 关键字 2.2.3 媒体特性 2.2 根据页面宽度改变颜色 2.3 媒体查询rem 实现元素动态大小变化 2.4 引入资源&#xff08;理解&#xff09; 2.4.1 语法规范 3. less基础 3.1 css弊端 3.2 less介绍…

基于SuperPoint与SuperGlue实现图像配准

基于SuperPoint与SuperGlue实现图像配准&#xff0c;项目地址https://github.com/magicleap/SuperGluePretrainedNetwork&#xff0c;使用到了特殊算子grid_sample&#xff0c;在转onnx时要求opset_version为16及以上&#xff08;即pytorch版本为1.9以上&#xff09;。SuperPoi…

计讯物联污染源自动监控系统,坚守“绿水青山就是金山银山”

近年来&#xff0c;“绿水青山就是金山银山”的理念在全国各地落地生根&#xff0c;各大城市积极构建环境监测体系&#xff0c;旨在让生态文明成色更足&#xff0c;绿色发展底色更亮。计讯物联污染源自动监控系统作为生态环境部门监督企业排污的“火眼金睛”&#xff0c;充分运…

apifox持续集成+java+企微机器人+xxljob定时推送

总览&#xff1a; apifox做接口测试后&#xff0c;把用例合并组装成测试套件&#xff0c;然后apifox-cli通过终端命令实现把套件执行后&#xff0c;输出本地文件的测试报告html或json。本地解析后拿到有用的解决通过定时执行推送到企微群里。 然后把html一起推到群里。 这个…

【Spark分布式内存计算框架——Spark SQL】8. Shuffle 分区数目、Dataset(上)

4.4 Shuffle 分区数目 运行上述程序时&#xff0c;查看WEB UI监控页面发现&#xff0c;某个Stage中有200个Task任务&#xff0c;也就是说RDD有200分区Partition。 原因&#xff1a;在SparkSQL中当Job中产生Shuffle时&#xff0c;默认的分区数&#xff08;spark.sql.shuffle.p…

基于STM32采用CS创世 SD NAND(贴片SD卡)完成FATFS文件系统移植与测试

一、前言 在STM32项目开发中&#xff0c;经常会用到存储芯片存储数据。 比如&#xff1a;关机时保存机器运行过程中的状态数据&#xff0c;上电再从存储芯片里读取数据恢复&#xff1b;在存储芯片里也会存放很多资源文件。比如&#xff0c;开机音乐&#xff0c;界面上的菜单图…

Selenium + python自动化测试环境搭建

selenium 是一个web的自动化测试工具&#xff0c;不少学习功能自动化的同学开始首选selenium &#xff0c;相因为它相比QTP有诸多有点&#xff1a; 免费&#xff0c;也不用再为破解QTP而大伤脑筋 小巧&#xff0c;对于不同的语言它只是一个包而已&#xff0c;而QTP需要下载安…

JSON字符串解析

目录 依赖 方法 示例 判断JSON是否合格 依赖 方法 JSON.parseObject() JSON.parseArray() 示例 Data public class OrderVo {public String name;public Integer price;public Integer count; } JSON数据 { "name": "苹果手机", "pric…

BIT.8_Linux 多线程

目录Linux线程概念什么是线程线程的优点线程的缺点线程异常线程用途Linux进程VS线程进程和线程总结Linux线程控制POSIX线程库创建线程线程ID及进程地址空间布局进程和线程ID区别内核层面&#xff1a;pid & tgid线程终止线程等待__thread 和 pthread_self()分离线程Linux线程…

《爆肝整理》保姆级系列教程python接口自动化(十七)--Json 数据处理---一次爬坑记(详解)

简介 有些 post 的请求参数是 json 格式的&#xff0c;这个前面发送post 请求里面提到过&#xff0c;需要导入 json模块处理。现在企业公司一般常见的接口因为json数据容易处理&#xff0c;所以绝大多数返回数据也是 json 格式的&#xff0c;我们在做判断时候&#xff0c;往往只…

Guava常用工具类总结

-“Null的含糊语义让人很不舒服。Null很少可以明确地表示某种语义&#xff0c;例如&#xff0c;Map.get(key)返回Null时&#xff0c;可能表示map中的值是null&#xff0c;亦或map中没有key对应的值。Null可以表示失败、成功或几乎任何情况。使用Null以外的特定值&#xff0c;会…

每日学术速递2.17

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.LG 1.Decoupled Model Schedule for Deep Learning Training 标题&#xff1a;深度学习训练的解耦模型时间表 作者&#xff1a;Hongzheng Chen, Cody Hao Yu, Shuai Zheng, Zhen Zhang,…

快速识别台式机的内存条

拿上一根内存条&#xff0c;让一个喜欢IT的识别一下&#xff0c;很多人不一定能说出点内容。 这很正常&#xff0c;IT细分领域太多了&#xff0c;很多搞IT的包括写代码的人可能都没有接触内存条。 硬件的集成度随着硬件技术的提升越来越高&#xff0c;成本也下来了&#xff0c;…

支付宝支付详细流程

1、二维码的生成二维码生成坐标 <!-- zxing生成二维码 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.3</version></dependency><dependency><groupId>co…