Tomcat请求处理流程

上一篇说到了Tomcat的启动流程,那么请求处理的流程是如何完成的呢?

Connector接收请求

Connector是请求接收器,通过设置的协议处理器处理请求,以默认的Http11NioProtocol协议为例,Http11NioProtocol构造时创建了NioEndpoint,用来处理网络请求:

public Http11NioProtocol() {
        super(new NioEndpoint());
    }

Connector启动时,启动了protocolHandler(Http11NioProtocol), protocolHandler启动的时候又对endpoint进行了启动,NioEndpoint启动过程中绑定了本地端口并在Acceptor线程中监听网络连接(详情可见Tomcat启动流程),所以Tomcat接收网络请求的起点是从Accetpor线程开始的。
Acceptor中会循环阻塞调用serverSock.accept()来监听请求:

......
try {
                    // Accept the next incoming connection from the server
                    // socket
                    socket = endpoint.serverSocketAccept();
                } catch (Exception ioe) {
                    // We didn't get a socket
                    endpoint.countDownConnection();
                    if (endpoint.isRunning()) {
                        // Introduce delay if necessary
                        errorDelay = handleExceptionWithDelay(errorDelay);
                        // re-throw
                        throw ioe;
                    } else {
                        break;
                    }
                }
// Successful accept, reset the error delay
                errorDelay = 0;

                // Configure the socket
                if (endpoint.isRunning() && !endpoint.isPaused()) {
                    // setSocketOptions() will hand the socket off to
                    // an appropriate processor if successful
                    if (!endpoint.setSocketOptions(socket)) {
                        endpoint.closeSocket(socket);
                    }
                } else {
                    endpoint.destroySocket(socket);
                }
......

接收到请求后,通过setSocketOptions来处理请求:

protected boolean setSocketOptions(SocketChannel socket) {
        NioSocketWrapper socketWrapper = null;
        try {
            // Allocate channel and wrapper
            NioChannel channel = null;
            if (nioChannels != null) {
                channel = nioChannels.pop();
            }
            if (channel == null) {
                SocketBufferHandler bufhandler = new SocketBufferHandler(
                        socketProperties.getAppReadBufSize(),
                        socketProperties.getAppWriteBufSize(),
                        socketProperties.getDirectBuffer());
                if (isSSLEnabled()) {
                    channel = new SecureNioChannel(bufhandler, selectorPool, this);
                } else {
                    channel = new NioChannel(bufhandler);
                }
            }
            NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);
            channel.reset(socket, newWrapper);
            connections.put(socket, newWrapper);
            socketWrapper = newWrapper;

            // Set socket properties
            // Disable blocking, polling will be used
            socket.configureBlocking(false);
            socketProperties.setProperties(socket.socket());

            socketWrapper.setReadTimeout(getConnectionTimeout());
            socketWrapper.setWriteTimeout(getConnectionTimeout());
            socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
            socketWrapper.setSecure(isSSLEnabled());
            poller.register(channel, socketWrapper);
            return true;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            try {
                log.error(sm.getString("endpoint.socketOptionsError"), t);
            } catch (Throwable tt) {
                ExceptionUtils.handleThrowable(tt);
            }
            if (socketWrapper == null) {
                destroySocket(socket);
            }
        }
        // Tell to close the socket if needed
        return false;
    }

代码比较长,最关键的代码是poller.register(channel, socketWrapper),将请求进来的socket包装后注册到poller中等待读写事件,这个地方的socket是非阻塞的,即可以用一个poller线程处理大量的请求连接,提高系统吞吐量。
接下来来到Poller类:

public void register(final NioChannel socket, final NioSocketWrapper socketWrapper) {
            socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
            PollerEvent r = null;
            if (eventCache != null) {
                r = eventCache.pop();
            }
            if (r == null) {
                r = new PollerEvent(socket, OP_REGISTER);
            } else {
                r.reset(socket, OP_REGISTER);
            }
            addEvent(r);
        }

Poller线程run方法:

@Override
        public void run() {
            // Loop until destroy() is called
            while (true) {

                boolean hasEvents = false;

                try {
                    if (!close) {
                        hasEvents = events();
                       ...
                            keyCount = selector.select(selectorTimeout);
                        ...
                        wakeupCounter.set(0);
                    }
                   ...
                } catch (Throwable x) {
                    ExceptionUtils.handleThrowable(x);
                    log.error(sm.getString("endpoint.nio.selectorLoopError"), x);
                    continue;
                }
                // Either we timed out or we woke up, process events first
                if (keyCount == 0) {
                    hasEvents = (hasEvents | events());
                }

                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()) {
                    SelectionKey sk = iterator.next();
                    NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                    processKey(sk, socketWrapper);
                }

代码进行了一些删减,只保留了主要流程:
1.Poller注册socket
Poller将连接上的socket设置interestOps(SelectionKey.OP_READ),包装成PollerEvent,interestOps为OP_REGISTER,放入队列中。
2.Poller线程从队列中取事件,执行run方法:

if (interestOps == OP_REGISTER) {
                try {
                    socket.getIOChannel().register(socket.getSocketWrapper().getPoller().getSelector(), SelectionKey.OP_READ, socket.getSocketWrapper());
                } catch (Exception x) {
                    log.error(sm.getString("endpoint.nio.registerFail"), x);
                }
            } 

将socket注册到Poller的Selector中,ops为SelectionKey.OP_READ,等待对端发送数据。
3.然后在socket有数据可读时,通过processKey(sk, socketWrapper)来进行处理。继续跟进processKey,最终将socket封装成socketProcessor提交到Executor处理。

public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = null;
            if (processorCache != null) {
                sc = processorCache.pop();
            }
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
                executor.execute(sc);
            } else {
                sc.run();
            }
......

SocketProcessor实现了Runnable接口,在线程池中执行SocketProcessor.doRun,忽略可能的TLS握手到流程,将通过连接处理器ConnectionHandler来处理请求:

getHandler().process(socketWrapper, SocketEvent.OPEN_READ);

ConnectHandler又通过Http11Processor来处理

if (processor == null) {
                    processor = getProtocol().createProcessor();
                    register(processor);
                }

               ...
                // Associate the processor with the connection
                wrapper.setCurrentProcessor(processor);

                SocketState state = SocketState.CLOSED;
                do {
                    state = processor.process(wrapper, status);

Http11Processor中会根据Http协议来解析数据,封装成request、response模型,并通过适配器转给Container,然后Connector的任务就完成了,接下来的操作由Container来进行。

getAdapter().service(request, response);

Adapter的处理

Connect的主要任务是接收请求,并在有数据读写时在线程池中根据使用的协议来解析数据并封装成request、response,然后交给Adapter来处理。
Adapter的实现类是CoyoteAdapter,顾名思义是一个适配器,将Connector连接器读取的数据适配到Container容器来处理。
Adapter主要有两个任务map和转发。
map在postParseRequest方法中进行:

connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());

根据请求到uri找到目标Host、Context、Wrapper。
Mapper对象存在service中,通过MapperListener来监听Container到生命周期,动态调整Mapper中的Container。
通过Mapper找到目标Container后,就可以转发给Container来处理了:

connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);

Container的处理流程

Adapter传给Container的操作是将request、response参数交给engine的pipeline处理。每一个级别的Container都维护了一个pipeline,用来处理请求,这是典型的流水线模式,pipeline中维护了一个Valve链表,请求在pipeline中处理的过程就是从第一个Valve处理到最后一个,Valve可以方便的进行配置来实现各层容器的扩展功能,每个pipeline的最后一个Valve是"basic",完成基本的逻辑处理,如Engine的basic将调用传给下一级的Host的pipeline,Host的basic将调用传给下一级的Context的pipeline,Context的basic将调用传给下一级的Wrapper的pipeline,如Engine的basic StandardEngineValve:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Host to be used for this Request
        Host host = request.getHost();
        if (host == null) {
            // HTTP 0.9 or HTTP 1.0 request without a host when no default host
            // is defined. This is handled by the CoyoteAdapter.
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        host.getPipeline().getFirst().invoke(request, response);
    }

而Wrapper作为最底层的Container,basic完成最终Servlet的加载、初始化,并组装filterChain进行调用:
standardWrapperValve.invoke部分代码

...
if (!unavailable) {
  servlet = wrapper.allocate();
}
...
// Create the filter chain for this request
ApplicationFilterChain filterChain =
  ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
...
filterChain.doFilter(request.getRequest(), response.getResponse());
...

那么什么时候调用了Servlet的service来处理请求呢?在filter链的调用过程中,链中所有的filter处理完成后,后执行Servlet.service来处理请求:

private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ...
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        //chain处理完成后,执行servlet.service
        try {
           ...
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
               ...
            } else {
                servlet.service(request, response);
            }

至此,tomcat已经完成了从接受连接请求到找到目标servlet来处理请求的流程,实现了作为Servlet容器的功能。

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

推荐阅读更多精彩内容