六 tomcat启动源码分析(三)--http请求nio处理

目标

分析tomcat处理一次http请求的nio过程模式,因为bio比较常见,网上好多分析资料可参考。

分析

上一节,我们已经分析了tomcat端启动流程,最后Connector中启动Endpoint进行端口监听,然后在mapperListener中存了相关各容器的映射关系,最后pipeline作为容器的调用管道链。按照这个流程,我们分析调用源码,进行debug,看下最终我的nio调用时序图:


时序图

简单语言描述下:

  1. 请求进入NioEndPoint的内部Acceptor接收请求,根据nio的异步处理机制,socket会作为一个pollerEvent事件存入队列,poller会轮询通过select进行选择可读事件
 protected class Acceptor implements Runnable {
        /**
         * The background thread that listens for incoming TCP/IP connections and
         * hands them off to an appropriate processor.
         */
        @Override
        public void run() {

            int errorDelay = 0;

            // Loop until we receive a shutdown command
            while (running) {
                   。。。。。。。。
                        Thread.sleep(1000);
                。。。。。。。
                        //每次用完一个新建
                        SocketChannel socket = null;
                        socket = serverSock.accept();
                   ..............
                    //此处去注册任务了,然后关闭此socket
                        if (!setSocketOptions(socket)) {
                            try {
                                socket.socket().close();
                                socket.close();
                  。。。。。。。。。。。。。。
        }//run
    }

对应的setSocketOptions方法

   protected boolean setSocketOptions(SocketChannel socket) {
            //获取NioChannel,没有空闲的则新建一个
            NioChannel channel = nioChannels.poll();
            。。。。。。。。。。           
          //注册poller事件任务
            getPoller0().register(channel);
          。。。。。。。
            return true;
    }
  1. selector找到待处理事件后开启异步线程调用SocketProcessor进行处理,SocketProcessor找到其对应的协议处理类封装request,最后通过CoyoteAdapter进行适配处理
    看下Poller中监听run代码
 public void run() {
            // Loop until we receive a shutdown command
            while (running) {
                try {
                    // Loop if endpoint is paused
                    while (paused && (!close) ) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // Ignore
                        }
                    }
                    //判断是否有待处理任务
                    boolean hasEvents = events();
                    //通过nio的selector选择可读事件,进行处理
                    Iterator<SelectionKey> iterator =
                        keyCount > 0 ? selector.selectedKeys().iterator() : null;
                    // Walk through the collection of ready keys and dispatch
                    // any active event.
                    while (iterator != null && iterator.hasNext()) {
                       
                            //处理此key
                            processKey(sk, attachment);
                   
        }

然后进入processKey处理数据,此方法最终调用processSocket(channel, null, true)方法

    public boolean processSocket(NioChannel socket, SocketStatus status, boolean dispatch) {
        try {
            KeyAttachment attachment = (KeyAttachment)socket.getAttachment(false);
            attachment.setCometNotify(false); //will get reset upon next reg
            SocketProcessor sc = processorCache.poll();
            if ( sc == null ) sc = new SocketProcessor(socket,status);
            else sc.reset(socket,status);
            //获取SocketProcessor处理器,如果配置了getExecutor则在异步线程中进行处理,否则直接处理
            if ( dispatch && getExecutor()!=null ) getExecutor().execute(sc);
            else sc.run();
        } catch (RejectedExecutionException rx) {
            log.warn("Socket processing request was rejected for:"+socket,rx);
            return false;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This means we got an OOM or similar creating a thread, or that
            // the pool and its queue are full
            log.error(sm.getString("endpoint.process.fail"), t);
            return false;
        }
        return true;
    }

在SocketProcessor的run方法中,核心部分我们看到
state = (status==null)?handler.process(socket):handler.event(socket,status);
这个handler为Http11NioProtocol,根据我们配置的协议形成,看下其process方法,最终找到其对应方法SocketState state = processor.process(socket); ,此时processor为协议对应的处理器Http11NioProcessor,简化此方法

 public SocketState process(NioChannel socket)
        throws IOException {
        RequestInfo rp = request.getRequestProcessor();
                。。。。。。。。               
                  //处理request数据,session,cookie等信息都在此时进行处理
                    prepareRequest();
                    //适配找到对应容器业务
                    adapter.service(request, response);
                。。。。。。
        }

    }
  1. CoyoteAdapter首先会解析request,最后调用pipeline调用链进入container
   public void service(org.apache.coyote.Request req, 
                        org.apache.coyote.Response res)
        throws Exception {

        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);
        //处理request和response信息
          .............         
      //此处会调用pipeline逐级调用进入engine、host、context、wrapper        connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

              ...............
              //成功后,返回信息
                response.finishResponse();
                if (postParseSuccess) {
                    // Log only if processing was invoked.
                    // If postParseRequest() failed, it has already logged it.
                    ((Context) request.getMappingData().context).logAccess(
                            request, response,
                            System.currentTimeMillis() - req.getStartTime(),
                            false);
                }
                req.action(ActionCode.POST_REQUEST , null);
            }

     。。。。。。。。。
        }

    }
  1. 在pipeline层层传递下最后进入StandardWrapperValve找到最终的servlet,匹配path对应的filter,包装filer链,调用filter的doFilter方法,最后进入servlet执行业务
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

       。。。。。
        //找到对应的servlet
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        Servlet servlet = null;
        Context context = (Context) wrapper.getParent();
        
        。。。。。。。。。
        //形成过滤器调用链
        ApplicationFilterFactory factory =
            ApplicationFilterFactory.getInstance();
        ApplicationFilterChain filterChain =
            factory.createFilterChain(request, wrapper, servlet);
        
       。。。。。。。

    }

  1. servlet执行完后,CoyoteAdapter会调用finishResponse方法关闭输出流,返回客户端。

知识点:

  1. tomcat中nio的实现,加深对nio概念理解

  2. 适配器模式进行接口适配

3.pipeline和filter等责任链模式的使用

参考资料:https://blog.csdn.net/sunyunjie361/article/details/60126398

目录: tomcat 源码学习系列
上一篇:   tomcat启动源码分析(二)--入口代码calatina启动介绍
下一篇:  tomcat启动源码分析(三)--http请求nio处理

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

推荐阅读更多精彩内容