Jetty源码导读二:接受请求过程

Stella981
• 阅读 588

Jetty的请求入口

ServerConnector.javaaccepted 方法(ServerSocketChannel#accept 后的处理逻辑)。

Jetty的请求流程

一个请求的流程:

  • 1.Acceptor 监听连接请求,当有连接请求到达时就接受连接,一个连接对应一个 Channel,Acceptor 将 Channel 交给 ManagedSelector 来处理。

  • 2.ManagedSelector 把 Channel 注册到 Selector 上,并创建一个 EndPointConnection 跟这个 Channel 绑定,接着就不断地检测 I/O 事件。

  • 3.I/O 事件到了就调用 EndPoint 的方法拿到一个 Runnable,并扔给线程池执行。

  • 4.线程池中调度某个线程执行 Runnable。

  • 5.Runnable 执行时,调用回调函数,这个回调函数是 Connection 注册到 EndPoint 中的。

  • 6.回调函数内部实现,其实就是调用 EndPoint 的接口方法来读数据。

  • 7.Connection 解析读到的数据,生成请求对象并交给 Handler 组件去处理。

SelectorManager

Jetty 的 Selector 由 SelectorManager 类管理,而被管理的 Selector 叫作 ManagedSelector。 (这里SelectorManager 的具体实现类是 ServerConnector的内部类 ServerConnectorManager。) SelectorManager 内部有一个 ManagedSelector 数组,真正干活的是 ManagedSelector。 主要做两个事情:

  • 选择一个 ManagedSelector 来处理 Channel

  • 提交一个任务 Accept 给 ManagedSelector

    // SelectorManager.java public void accept(SelectableChannel channel, Object attachment) { // 选择一个 ManagedSelector 来处理 Channel final ManagedSelector selector = chooseSelector(); // 提交一个任务 Accept 给 ManagedSelector selector.submit(selector.new Accept(channel, attachment)); }

ManagedSelector

ManagedSelector 在处理这个任务Accept 主要做了两步:

  • 第一步,调用 Selector 的 register 方法把 Channel 注册到 Selector 上,拿到一个 SelectionKey。

    // ManagedSelector$Accept.java 内部类 @Override public void update(Selector selector){ try{ // 把 Channel 注册到 Selector 上,拿到一个 SelectionKey key = channel.register(selector, 0, attachment); execute(this); // 执行当前Runnable, 跳转 run() } catch (Throwable x) { IO.close(channel); _selectorManager.onAcceptFailed(channel, x); LOG.debug(x); } } @Override public void run(){ try{ // 创建一个 EndPoint 和 Connection,并跟这个 SelectionKey(Channel)绑在一起 createEndPoint(channel, key); _selectorManager.onAccepted(channel); } catch (Throwable x){ LOG.debug(x); failed(x); } }

  • 第二步,创建一个 EndPointConnection,并跟这个 SelectionKey(Channel)绑在一起:

    // ManagedSelector.java private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException{ //1. 创建 Endpoint EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey); //2. 创建 Connection Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment()); //3. 把 Endpoint、Connection 和 SelectionKey 绑在一起 endPoint.setConnection(connection); selectionKey.attach(endPoint); endPoint.onOpen(); endPointOpened(endPoint); // 将Connection 注入到 EndPoint, 跳转 Connection 的 onOpen 方法 (内部转 fillInterested) _selectorManager.connectionOpened(connection); }

如上,HttpConnection (Connection 的具体实现类之一) 并不会主动向 EndPoint 读取数据,而是向在 EndPoint 中注册一堆回调方法:

// HttpConnection.java
public void onOpen(){
    super.onOpen();
    if (isRequestBufferEmpty())
        fillInterested();
    else
        getExecutor().execute(this);
}
public void fillInterested(){
    // 告诉 EndPoint,数据到了你就调我这些回调方法 _readCallback 吧,有点异步 I/O 的感觉,也就是说 Jetty 在应用层面模拟了异步 I/O 模型。
    getEndPoint().fillInterested(_readCallback);
}
ManagedSelector 的 EatWhatYouKill

这时候,ManagedSelector 启动时候的任务 EatWhatYouKill 的 无限循环,监测到SelectorProducerprocessSelected 方法选择 出一个 Runnable 则,会自动执行。 这个 Runnable 从上步骤来看,则是 调用EndPoint 的 onSelected 方法返回一个 Runnable,然后把这个 Runnable 直接执行或扔给线程池执行。

// ManagedSelector.java
_selectorManager.execute(_strategy::produce);




// ManagedSelector$SelectorProducer.java
public Runnable produce() {
    while (true) {
        // 查看key绑定的 EndPoint 是否有可执行的回调函数 (读 或 写 或 NULL)
        // 获得后递交全局线程池执行
        // 或 ManagedSelector$Acceptor 执行接受客户端连接的函数, 返回 NULL
        Runnable task = processSelected();
        if (task != null) {
            return task;
        }
        // 执行 ManagedSelector$Acceptor 注册 SelectionKey.OP_ACCEPT 接受事件
        processUpdates();
        // 执行 Endpoint 的 update 方法,切换注册 读、写事件
        updateKeys();
        // 唤醒select, 获取就绪的key
        if (!select()) {
            return null;
        }
    }
}

具体里面就是 调用 EndPointonSelected方法

// ManagedSelector$SelectorProducer.java
...
if (attachment instanceof Selectable){
    // Try to produce a task
    Runnable task = ((Selectable)attachment).onSelected();
    if (task != null)
        return task;
}

EndPoint (ChannelEndPoint)

(这里EndPoint 的具体实现类是 ChannelEndPoint

这里怎么选择出这个 Runnable 呢??

// ChannelEndPoint.java

    public Runnable onSelected() {

        ....

        boolean fillable = (readyOps & SelectionKey.OP_READ) != 0;

        boolean flushable = (readyOps & SelectionKey.OP_WRITE) != 0;

        // return task to complete the job

        Runnable task = fillable

            ? (flushable

            ? _runCompleteWriteFillable

            : _runFillable)

            : (flushable

            ? _runCompleteWrite

            : null);

            

        return task;

    }

很简单,依据 selector 监听到连接上是读就绪(channel通道中有数据可读),还算写就绪(channel通道中有数据可写)。

Connection (HttpConnection)

  • 这里如果是读,则进来 AbstractConnection$ReadCallback ,即是 HttpConnection 的 onFillable() 方法

    调用 EndPoint 的接口去读数据,读完后让 HTTP 解析器HttpParser去解析字节流,HTTP 解析器会将解析后的数据,包括请求行、请求头相关信息存到 Request 对象里, 然后丢给我们的 第一个Handler。

    //HttpConnection.java

    public void onFillable(){

    // HTTP 解析器去解析字节流
    
    boolean handle = parseRequestBuffer();
    
    ...
    
    // 将解析后的数据,包括请求行、请求头相关信息存到 Request 对象, 然后丢给我们的 第一个Handler
    
    if (handle){
    
    boolean suspended = !_channel.handle();
    
    ...
    

    }

    //HttpChannel.java

    public boolean handle(){

    ...

    dispatch(DispatcherType.REQUEST, () -> {

            for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers()) {
    
                customizer.customize(getConnector(), _configuration, _request);
    
                if (_request.isHandled())
    
                    return;
    
            }
    
            getServer().handle(HttpChannel.this);
    
        });
    

    ....

    }

比如HttpChannelOverHttp (HttpChannel的具体实现类之一)

  • 处理完后, AbstractConnection$SendCallback调用 EndPoint的 写方法

相关

Jetty源码导读一:启动过程 [https://my.oschina.net/langxSpirit/blog/3144656)

by 斯武丶风晴 https://my.oschina.net/langxSpirit

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Souleigh ✨ Souleigh ✨
2年前
前端性能优化 - 雅虎军规
无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法参考文章:(1)Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.codeprj.com%2Fblo
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这