Tomcat 源码分析 一次完整请求 (基于8.0.5)

1. Tomcat 一次完整请求猜想

在进行分析之前, 我们先自己猜想一下, Tomcat 处理一个请求一共完成哪些步骤:

(1) Acceptor 接受请求
(2) 交给工作线程池来处理请求
(3) 封装请求成 Request, Response 对象, 向后端容器进行传递
(4) 经由后端容器的一个一个 Valve 处理
(5) 在 StandardWrapperValve 根据URi, 组装 ApplicationFilterChain 来处理请求
(6) 请求经过 Servlet 里面的逻辑处理
(7) 请求处理的结果经过 Response 对象刷到 浏览器上
2. Tomcat Acceptor处理请求

Acceptor是一个专门处理请求连接得线程, 他主要做了以下步骤:

1. 在没有网络 IO 数据时, 该线程会一直 在 serverSocketFactory.acceptSocket 上阻塞
2. 如果有请求, 则首先通过 countUpOrAwaitConnection 来获取请求处理得许可(用的是LimitLatch)
3. 若获取 LimitLatch 成功, 则首先将 socket 数据设置 Connector 配置的一些属性, 
4. 封装成 SocketProcessor 交由工作线程池来处理

见代码:

try {
    socket = serverSocketFactory.acceptSocket(serverSocket);    // 1. 获取链接请求
} catch (IOException ioe) {
    countDownConnection();
}

if (running && !paused && setSocketOptions(socket)) {           // 2. setSocketOptions 是设置socket属性的方法 
    // Hand this socket off to an appropriate processor
    if (!processSocket(socket)) {                               // 3. 在processSocket里面封装一个 SocketProcessor交给线程池处理
        countDownConnection();
        closeSocket(socket);
    }
}

从上面得代码中获知: Connector 是通过 LimitLatch 来控制连接处理数, 所以 BIO 情况下的 tomcat 并发请求数 = backlog + LimitLatch.maxConnection, 还有另外一个: socket常见的属性:

1. soLinger: 是在调用 socket.close() 时阻塞一会, 因为底层的数据 可能还没有发出去
2. soTimeOut: 这个参数是在 ServerSocket.accept() 时阻塞的时间, 若超过这个时间还没有客户端连接上来, 则直接报出 SocketTimeoutException 异常, 当 ServerSocket 还是存活着的
   参考地址 https://docs.oracle.com/javase/8/docs/api/java/net/ServerSocket.html#setSoTimeout-int-
   与之对应的是 客户端的 socket, 这时 soTimeOut 影响的是 inputStream.read() 的超时时间
   参考地址 https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html#setSoTimeout-int-
3. tcpNoDelay: Nagle's 算法主要是减少小数据包在网络上的流动,当数据包小于 limit (usually MSS), 将会等待前面发送的数据包返回  ACK(这意味着在底层积累数据包, 等到数据包变大了再发送出去)
   而 TcpNoDelay 主要是禁止 Nagle 算法
   参考地址
   https://stackoverflow.com/questions/3761276/when-should-i-use-tcp-nodelay-and-when-tcp-cork
   http://ccr.sigcomm.org/archive/2001/jan01/ccr-200101-mogul.pdf
3. Tomcat SocketProcessor

SocketProcessor里面就开始进行正真的请求处理, 主要步骤:

1. 处理SSL握手
2. 直接调用 Http11Protocol$Http11ConnectionHandler.process()来 处理请求

在Http11Protocol$Http11ConnectionHandler.process()里面最奇怪的是请求的处理是在一个loop里面, 这是为什么呢? 主要是因为 http 特性里面 keepAlive 的存在, 即一个 socket 的存在能处理多次 http 请求(PS: 那多少次呢, 是否有超时时间限制, 这些参数具体看配置了,看 XXXProtocol.createProcessor 方法里面 )
下面我们再来看看传说中的Http11Processor

protected Http11Processor createProcessor() {                          // 构建 Http11Processor
    Http11Processor processor = new Http11Processor(
            proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint, // 1. http header 的最大尺寸
            proto.getMaxTrailerSize(),proto.getMaxExtensionSize());
    processor.setAdapter(proto.getAdapter());
    processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());// 2. 默认的 KeepAlive 情况下, 每个 Socket 处理的最多的 请求次数
    processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());        // 3. 开启 KeepAlive 的 Timeout
    processor.setConnectionUploadTimeout(
            proto.getConnectionUploadTimeout());                       // 4. http 当遇到文件上传时的 默认超时时间 (300 * 1000)
    processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
    processor.setCompressionMinSize(proto.getCompressionMinSize());    // 5. 当 http 请求的 body size超过这个值时, 通过 gzip 进行压缩
    processor.setCompression(proto.getCompression());                  // 6. http 请求是否开启 compression 处理
    processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
    processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());// 7. http body里面的内容是 "text/html,text/xml,text/plain" 才会进行 压缩处理
    processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
    processor.setSocketBuffer(proto.getSocketBuffer());                // 8. socket 的 buffer, 默认 9000
    processor.setMaxSavePostSize(proto.getMaxSavePostSize());          // 9. 最大的 Post 处理尺寸的大小 4 * 1000
    processor.setServer(proto.getServer());
    processor.setDisableKeepAlivePercentage(
            proto.getDisableKeepAlivePercentage());                    // 10. 这是一个阀值, 当工作线程池超过 disableKeepAlivePercentage() 默认时, 就会 disable 的功能, 在 AbstractHttp11Processor中的 process
    register(processor);                                               // 11. 将 Http11Processor 注册到 JMX 里面
    return processor;
}
4. Tomcat AbstractHttp11Processor

主逻辑:

1. 只解析 Http 请求头中的 方法, URI, 协议(PS: 这里是直接整块读取 Header 里面的数据, 最大大小是 8M, 见 Http11Processor 的构造函数)
2. 设置最大的 Header 大小
3. 解析 HTTP 请求的包问头 headers
4. 根据请求来设置其对应的 InputFilter
5. 调用 CoyoteAdapter 的 service 方法来处理请求

见代码:

if (!getInputBuffer().parseRequestLine(keptAlive)) {             // 只解析 Http 请求头中的 方法, URI, 协议(PS: 这里是直接整块读取 Header 里面的数据, 最大大小是 8M, 见 Http11Processor 的构造函数)
    if (handleIncompleteRequestLineRead()) {
        break;
    }
}
request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount()); // 设置最大的 Header 大小
// Currently only NIO will ever return false here
if (!getInputBuffer().parseHeaders()) {                          // 解析 HTTP 请求的包问头 headers
    // We've read part of the request, don't recycle it
    // instead associate it with the socket
    openSocket = true;
    readComplete = false;
    break;
}


/**
 * Parse the HTTP headers.
 * 读取请求头
 */
@Override
public boolean parseHeaders()
    throws IOException {
    if (!parsingHeader) {
        throw new IllegalStateException(
                sm.getString("iib.parseheaders.ise.error"));
    }

    while (parseHeader()) {             // 这里其实就是在 解析 Http header 里面的键值对 (loop 一次, 读取一行数据, 处理逻辑还是比较简单)
        // Loop until we run out of headers
    }

    parsingHeader = false;
    end = pos;
    return true;
}

if (!error) {
    // Setting up filters, and parse some request headers
    rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
    try {
        prepareRequest();                       // 根据请求来设置其对应的 InputFilter
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        if (getLog().isDebugEnabled()) {
            getLog().debug(sm.getString(
                    "http11processor.request.prepare"), t);
        }
        // 400 - Internal Server Error
        response.setStatus(400);
        getAdapter().log(request, response, 0);
        error = true;
    }
}
// 调用 CoyoteAdapter 的 service 方法, 传入 org.apache.coyote.Request对象及
// org.apache.coyoteResponse 对象
getAdapter().service(request, response);
5. Tomcat CoyoteAdapter

适配器模式: 主要将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题
对于适配器模式, 一般的书本都是这样定义的, 现在我们看看Tomcat里面是在哪种情况下运用的: 将网络框架的封装类 Request 转换成 Servlet容器能处理的Request, 见代码(connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);), 主要还是在 CoyoteAdapter.service()里面

主要步骤:

1. 通过 Connector 创建 org.apache.catalina.connector.Request对象,org.apache.catalina.connectorResponse 对象 这里的 Request, Response 实现了 ServletRequest/ServletResponse, 并且在内部属性中拥有 org.apache.coyote.Request/Response
2. 下面的 postParseRequest 是用来处理请求映射 (获取 host, context, wrapper, URI 后面的参数的解析, sessionId )
3. 开始调用 Tomcat 的容器, 首先调用 StandardEngine 容器中的管道PipeLine 中的第一个 Valve, 传入 connector.Request 与 connector.Response 来处理所有逻辑
4. 通过request.finishRequest 与 response.finishResponse(刷OutputBuffer中的数据到浏览器) 来完成整个请求

见下面的代码:

Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);

if (request == null) {
                                            // 1. 通过 Connector 创建 org.apache.catalina.connector.Request对象,org.apache.catalina.connectorResponse 对象 这里的 Request, Response 实现了 ServletRequest/ServletResponse, 并且在内部属性中拥有 org.apache.coyote.Request/Response
    request = connector.createRequest();    // 2. 这里的 Request 或实现 ServletRequest接口, 并且会传递到下游容器中 (PS: 在创建 Connector 时会构建一个 CoyoteAdapter(Connector))
                                            // 3. 设置 org.apache.coyote.Request 对象
    request.setCoyoteRequest(req);
                                            // 4. 通过 Connector 创建 org.apache.catalina.connectorResponse 对象
    response = connector.createResponse();
                                            // 5. 设置 org.apache.coyote.Response 对象
    response.setCoyoteResponse(res);
    // Link objects
                                            // 6. 把 Resquest 及 Response 对象相互关联起来
    request.setResponse(response);
    response.setRequest(request);

    // Set as notes
    req.setNote(ADAPTER_NOTES, request);
    res.setNote(ADAPTER_NOTES, response);

    // Set query string encoding
    req.getParameters().setQueryStringEncoding // 7. 获取 URI 的编码格式
        (connector.getURIEncoding());

}

req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());// 8. 在 RequestInfo 里面设置对应的 ThreadName (PS: 可以看到 这里 ThreadLocal 只有对对应 get, 但没有对应的 remove, 为什么呢?  因为这里存储 Tomcat 工作线程池的领地,  工作线程池 never stop, 除非停止 Tomcat, 并且这里缓存的数据的大小也是非常小的, 也不会与其他的 WebappClassLoader/WebappClassLoader 任何挂钩)
                                                                 // 9. 下面的 postParseRequest 是用来处理请求映射 (获取 host, context, wrapper, URI 后面的参数的解析, sessionId )
boolean postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
    //check valves if we support async
    request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
    // Calling the container
                                                                // 10. 开始调用 Tomcat 的容器, 首先调用 StandardEngine 容器中的管道PipeLine 中的第一个 Valve, 传入 connector.Request 与 connector.Response
    connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
}

                                                        // 11. 完成此次请求
request.finishRequest();
                                                        // 12. 完成此次请求, 并提交响应信息
response.finishResponse();                              // 13. 跟过源码的兄弟会发现, 这里有点绕, 主要是将 org.apache.catalina.connector.Response对应的 OutputBuffer 中的数据 刷到 org.apache.coyote.Response 对应的 InternalOutputBuffer 中, 并且最终调用 socket对应的 outputStream 将数据刷出去( 这里会组装 Http Response 中的 header 与 body 里面的数据, 并且刷到远端 )
if (postParseSuccess &&
        request.getMappingData().context != null) {
    // Log only if processing was invoked.
    // If postParseRequest() failed, it has already logged it.
    // If context is null this was the start of a comet request
    // that failed and has already been logged.
    request.getMappingData().context.logAccess(
            request, response,
            System.currentTimeMillis() - req.getStartTime(),
            false);
}

上述方法中比较重要的就是postParseRequest方法, 它主要完成:

1. 将URI里面的请求参数解析到 request.pathParameters 里面
2. Mapper.map 方法按照请求路径进行匹配, Mapper是路由程序, 主要根据 URI 中的信息, 匹配对应的 StandardHost, StandardContext, StandardWrapper
3. 根据默认的session追踪机制(defaultSessionTrackingModes)来尝试获取一下 sessionID(依次从 URI, Cookie, SSL中)
6. Tomcat StandardEngine

程序从CoyoteAdapter中找到对应的StandardEngine的Pipeline里面的第一个Valve来进行处理, 下面我们来看 StandardEngineValve
见代码:

/*
 * StandardEngine 容器默认配置了 StandardEngineValve 阀门, 它主要做负责选择响应的 Host 去处理请求
 */
@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // Select the Host to be used for this Request
                                                // 1. 得到此次请求对应的 StandardHost 容器
    Host host = request.getHost();              // 2. 通过 Mapper 模块是就已经获取 对应的 StandardHost
    if (host == null) {
        response.sendError
            (HttpServletResponse.SC_BAD_REQUEST,
             sm.getString("standardEngine.noHost",
                          request.getServerName()));
        return;
    }
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(host.getPipeline().isAsyncSupported());
    }
    // Ask this Host to process this request
                                                // 3. 调用 StandardHost 容器中管道 Pipeline 中的第一个 Valve
    host.getPipeline().getFirst().invoke(request, response);  

}

从代码中我们得知: 它主要做负责选择响应的 Host 去处理请求

7. Tomcat StandardHost

StandardHost的Pipeline里面一定有 ErrorReportValve, 与 StandardHostValve两个Valve
ErrorReportValve主要是检测 Http 请求过程中是否出现过什么异常, 有异常的话, 直接拼装 html 页面, 输出到客户端
见代码:

public void invoke(Request request, Response response)
    throws IOException, ServletException {
    // Perform the request
    getNext().invoke(request, response);        // 1. 先将 请求转发给下一个 Valve
    if (response.isCommitted()) {               // 2. 这里的 isCommitted 表明, 请求是正常处理结束
        return;
    }
    Throwable throwable =                       // 3. 判断请求过程中是否有异常发生
            (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
    if (request.isAsyncStarted() && ((response.getStatus() < 400 &&
            throwable == null) || request.isAsyncDispatching())) {
        return;
    }
    if (throwable != null) {
        // The response is an error
        response.setError();
        // Reset the response (if possible)
        try {
            response.reset();                  // 4. 重置 response 里面的数据(此时 Response 里面可能有些数据)
        } catch (IllegalStateException e) {
            // Ignore
        }
        response.sendError                     // 5. 这就是我们常看到的 500 错误码
            (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
    response.setSuspended(false);
    try {
        report(request, response, throwable); // 6. 这里就是将 异常的堆栈信息组合成 html 页面, 输出到前台                                          
    } catch (Throwable tt) {
        ExceptionUtils.handleThrowable(tt);
    }
    if (request.isAsyncStarted()) {          // 7. 若是异步请求的话, 设置对应的 complete (对应的是 异步 Servlet)                                              
        request.getAsyncContext().complete();
    }
}

上面逻辑比较简单, 看代码就行; 至于 StandardHostValve, 其主要根据请求的信息将其路由到对应的 StandardContext (PS: 其中比较重要的是, 在每次进行操作前后, 需要更改对应线程的 ContextClassloader 为 StandardContext 中的 WebappClassloader)

8. Tomcat StandardContext

StandardContext中的Valve也主要 根据 Request 里面的信息, 将请求路由到对应的 wrapper 中, 见代码

public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // Disallow any direct access to resources under WEB-INF or META-INF
    MessageBytes requestPathMB = request.getRequestPathMB();       // 1. 对于 WEB-INF 与 META-INF 目录禁止访问的控制
    if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/META-INF"))
            || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);      // 2. 返回 HTTP 状态码 404
        return;
    }
    // Select the Wrapper to be used for this Request
    Wrapper wrapper = request.getWrapper();                        // 3. 得到此请求对应的 StandardWrapper 容器 (这个是在路由模块获取到的)
    if (wrapper == null || wrapper.isUnavailable()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // Acknowledge the request
    try {
        response.sendAcknowledgement();
    } catch (IOException ioe) {
        container.getLogger().error(sm.getString(
                "standardContextValve.acknowledgeException"), ioe);
        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return;
    }

    if (request.isAsyncSupported()) {
        request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
    }
    wrapper.getPipeline().getFirst().invoke(request, response);    // 4. 调用 StandardWrapper 容器中管道 Pipeline 中的第一个 Valve 的 invoke() 方法
} 
9. Tomcat StandardWrapper

在StandardWrapper中的Valve比较重要, 见代码

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

    // Initialize local variables we may need
    boolean unavailable = false;
    Throwable throwable = null;
    // This should be a Request attribute...
    long t1=System.currentTimeMillis();                        // 1. 开始记录请求处理时间
    requestCount.incrementAndGet();                            // 2. 增加请求次数
    // 得到 StandardWrapper 容器
    StandardWrapper wrapper = (StandardWrapper) getContainer();// 3. 每个请求都会对应相应的 StandardWrapper 及 StandardWrapperValve 对象
    // 此次请求对应的 servlet
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();           // 4. 得到此次请求的 StandardContext 对象

    // Allocate a servlet instance to process this request
    try {
        if (!unavailable) { // 判断 Servlet 是否存在
            // 从 StandardWrapper 容器获取一个 Servlet 对象, Servlet对象的创建及初始化init 都在这里执行
            /**
             * Servlet 的分配操作,
             * 在 !SingleThreadModel 模式下, 多线程共享一个 Servlet
             * 在  SingleThreadModel 模式下, Servlet 放到一个共享的对象池里面(池里面最多放 20 个 Servlet)
             */
            servlet = wrapper.allocate();                     // 6. 进行 servlet 的分配
        }
    } catch (UnavailableException e) {}

    MessageBytes requestPathMB = request.getRequestPathMB();  // 7. 获取 请求的 Path
    DispatcherType dispatcherType = DispatcherType.REQUEST;
    if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
    request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
    request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
            requestPathMB);

    // Create the filter chain for this request
    ApplicationFilterFactory factory =                        // 8. 创建 ApplicationFilterFactory 对象
        ApplicationFilterFactory.getInstance();

    // 创建 ApplicationFilterChain
    ApplicationFilterChain filterChain =                      // 9. 创建此次请求的 ApplicationFilterChain 对象, 包装了所有请求的 Servlet 对象及一些拦截的过滤器 Filter 对象
        factory.createFilterChain(request, wrapper, servlet);

    // 调用 ApplicationFilterChain的 doFilter 方法
    // 传入 org.apache.catalina.connector.RequestFacade 及
    // org.apache.catalina.connector.ResponseFacade 对象, 开始进行请求处理
    filterChain.doFilter(request.getRequest(),                // 10.  执行 filterChain 链, 在链的末尾就是 servlet
            response.getResponse());

    // Release the filter chain (if any) for this request
    if (filterChain != null) {
        if (request.isComet()) {
            // If this is a Comet request, then the same chain will be used for the
            // processing of all subsequent events.
            filterChain.reuse();
        } else {
            filterChain.release();                            // 11. 释放 对应的 ApplicationFilterChain里面的资源
        }
    }

    / Deallocate the allocated servlet instance
    try {
        if (servlet != null) {
            wrapper.deallocate(servlet);                      // 12. 执行 完后把 Servlet 实例回收到 Servlet 实例池
        }
    } catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString("standardWrapper.deallocateException",
                         wrapper.getName()), e);
        if (throwable == null) {
            throwable = e;
            exception(request, response, e);
        }
    }

    long t2=System.currentTimeMillis();
    long time=t2-t1;
    processingTime += time;                                  // 13. 这里的 processingTime 就是请求的处理时间
    if( time > maxTime) maxTime=time;
    if( time < minTime) minTime=time;

}

上面的代码中比较重要的是

1. 进行 servlet 的分配
2. 创建此次请求的 ApplicationFilterChain 对象, 包装了所有请求的 Servlet 对象及一些拦截的过滤器 Filter 对象
3. 通过执行 filterChain 链, 在链的末尾就是 servlet

首先看Servlet的分配

public Servlet allocate() throws ServletException {
    // If we are currently unloading this servlet, throw an exception
    if (unloading)
        throw new ServletException
          (sm.getString("standardWrapper.unloading", getName()));
    boolean newInstance = false;
    // If not SingleThreadedModel, return the same instance every time
    if (!singleThreadModel) {               // 1. 是否是 单个线程单个实例 的模型(PS: 这里的 Servlet 若是已经实例化了, 则说明 在 load-on-startup 时已经实例化了)
        /**
         * 若 instance 存在的话, 说明要么
         * 1. 在 web.xml 中的 load-on-startup 已经实例化过
         * 2. 要么这个实例化的流程已经走过了一遍
         */
        // Load and initialize our instance if necessary
        if (instance == null) {             // 2. double check lock 通过 double check lock 来实例化
            synchronized (this) {
                if (instance == null) {     // 3. 说明 Servlet 没有实例化过
                    try {
                        if (log.isDebugEnabled())
                            log.debug("Allocating non-STM instance");

                        instance = loadServlet();// 4. 通过 InstanceManager 来实例化
                        if (!singleThreadModel) {
                            // For non-STM, increment here to prevent a race
                            // condition with unload. Bug 43683, test case
                            // #3
                            newInstance = true;
                            countAllocated.incrementAndGet();
                        }
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException
                            (sm.getString("standardWrapper.allocate"), e);
                    }
                }
            }
        }
        if (!instanceInitialized) {              // 5. 调用 Servlet 的初始化方法 init
            initServlet(instance);
        }

        if (singleThreadModel) {                 // 6. 若是单例模式, 就直接放到  instancePool 里面
            if (newInstance) {
                // Have to do this outside of the sync above to prevent a
                // possible deadlock
                synchronized (instancePool) {
                    instancePool.push(instance);
                    nInstances++;
                }
            }
        } else {
            if (log.isTraceEnabled())
                log.trace("  Returning non-STM instance");
            // For new instances, count will have been incremented at the
            // time of creation
            if (!newInstance) {
                countAllocated.incrementAndGet();
            }
            return (instance);
        }
    }
    synchronized (instancePool) {
        while (countAllocated.get() >= nInstances) { // 7. 下面的这段代码可以用 Semaphore 之类的来进行取代 (个人还是比较喜欢 JUC 里面的代码)
            // Allocate a new instance if possible, or else wait
            if (nInstances < maxInstances) {         // 8. 这里的 maxInstances = 20 表示在 一个线程一个 Servlet 的模式下, 对象池中最多只能有 20 个Servlet
                try {
                    instancePool.push(loadServlet());// 9. 将加载好的 Servlet 放入对象池里面
                    nInstances++;
                } catch (ServletException e) {
                    throw e;
                } catch (Throwable e) {
                    ExceptionUtils.handleThrowable(e);
                    throw new ServletException
                        (sm.getString("standardWrapper.allocate"), e);
                }
            } else {
                try {
                    instancePool.wait();            // 10. 在 Servlet.deallocate 中会进行 instancePool.notify 通知唤醒
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
        }
        if (log.isTraceEnabled())
            log.trace("  Returning allocated STM instance");
        countAllocated.incrementAndGet();
        return instancePool.pop();
    }
}

Servlet 的分配操作主要分为下面两种模式
在 !SingleThreadModel 模式下, 多线程共享一个 Servlet
在 SingleThreadModel 模式下, Servlet 放到一个共享的对象池里面(池里面最多放 20 个 Servlet, 这里只同一个Servlet Class 的实例)
见 InstanceManager 来分配 Servlet, 其中最有感觉的是 Servlet 的Annotation属性的注入(PS: 这些属性的解析获取是在 ContextConfig.applicationAnnotationsConfig() 里面进行设置)

// 通过 InstanceManager 来对 Servlet 实例进行创建
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
    // servlet 的实例化不是通过 class.forname 来生成的
    // 下面的方法就会涉及到 @inject 如何注入, 并且在其执行时候, 是怎么进行查找的
    servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {}

@Override
public Object newInstance(String className) throws IllegalAccessException,
        InvocationTargetException, NamingException, InstantiationException,
        ClassNotFoundException {
    Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
    return newInstance(clazz.newInstance(), clazz);
}

private Object newInstance(Object instance, Class<?> clazz)
        throws IllegalAccessException, InvocationTargetException, NamingException {
    if (!ignoreAnnotations) { // 找到当前的 inject 点, 从 injectionMap 中查找出当前 Servlet 的 Inject集合
        /**
         * 主要有
         * @Resource
         * @WebServiceRef
         * @PersistenceContext
         * @PersistenceUnit
         * @PostConstruct
         * @PreDestory
         */
        /**
         * 前面定义的应用拳击的注入集合 injectionMap, 是基于所有应用的, 而这里是基于特定的 Servlet 的
         * , 所以需要从 injectMap 中 get到自己的 Servlet 的inject集合
         */
        Map<String, String> injections = assembleInjectionsFromClassHierarchy(clazz);                       // 从 Servlet 类的继承树中收集 Servlet的注解
        /**
         * 在前面我们得到 injectionMap 集合, 这个集合的 value 不是引用的本身, 而是
         *  jndiName, 之所以没有将这些引用直接实例化是因为 对于这些引用非常占用内存, 并且初始化的时间非常长
         *  我们是否想过一个好的办法, 假设每一次都引用, 是否将这些都缓存起来, 第一次虽然费点劲, 初始化时间长, 而
         *  下一次就直接可以跳过初始化这一步, 具体操作在 populateAnnotationsCache
         *  Tomcat 将每一个 Annotation 条目都做成了 AnnotationCacheEntry, 这一步主要是将这些
         *  映射关系建立起来, 并没有直接把引用创建出来, 直接赋值到 AnnotationCacheEntry 中,
         *  操作已经在 processAnnotations 完成
         */
        populateAnnotationsCache(clazz, injections); // 将jndi的引用实例化为 annotationCache引用集合, 并进行缓存起来

        /**
         * 将引用存入 AnnotationCacheEntry 中去
         * 通过 tomcat 自身的 JNDI 系统进行查询, 如果是方法的化, 再进行
         *  method.invoke, 如果是 field 的话, 直接返回 filed 即可,
         *  当这一步操作完以后, AnnotationCacheEntry 就缓存完毕, 下一次再请求 Servlet的话
         *  实例化就不需要这些步骤的
         *  目前 Servlet 都是单实例多线程的
         */
        processAnnotations(instance, injections);    // 根据注解说明, 调用 Servlet 的方法, 进行设置名称上下文的资源

        /**
         * 对于 PostConstruct 的方法的回调,这个是为了 JAVA EE 规范的 Common Annotation 规范
         * 整体的思路也是查询方法, 然后进行回调注入的方法
         */
        postConstruct(instance, clazz); // 实例化 Object // 设置 @PostConstruct/@PreDestory 类型的资源依赖
    }
    return instance;
}

至于 ApplicationFilterChain 的组装, 在创建 ApplicationFilterChain 的过程中, 会遍历 filterMaps, 将符合 URL 请求的 Filter 加入到 filterChain 里面(代码比较简单, 就不贴出来了)

ApplicationFilterChain.internalDoFilter里面是一步一步的执行Filter, 若有一个Filter执行失败, 则在第一个判断里面return, 若都成功, 则向下执行Servlet

private void internalDoFilter(ServletRequest request,
                              ServletResponse response)
    throws IOException, ServletException {
    // Call the next filter if there is one
    if (pos < n) {                                              // 1. 如果还有 过滤器Filter, 则执行Filter
        ApplicationFilterConfig filterConfig = filters[pos++];  // 2. 得到 过滤器 Filter
        Filter filter = null;
        try {
            filter = filterConfig.getFilter();                  // 3. 这里的 getFilter在没有初始化Filter时, 会通过instanceManager来实现加载Filter(并且初始化Filter)
            support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                                      filter, request, response);

            // 执行过滤器 Filter的 doFilter(Request, Response, FilterChain) 方法     // 一个小建议 在 Fiter 下面加一个 FilterBase, 做些基础工作
                filter.doFilter(request, response, this);       // 4. 这里的 filter 的执行 有点递归的感觉, 只是通过 pos 来控制从 filterChain 里面拿出那个 filter 来进行操作 (所以在 Filter 里面所调用 return, 则会终止 Filter 的调用, 而下面的 Servlet.service 更本就没有调用到)
        }catch(Exception e){}

        return;                                                 // 5. 这里return 表明没有所有 Filter 都执行成功
    }     

    servlet.service(request, response);                         // 6. 过滤器 Filter 全部执行完, 最终调用 servlet 的 service(request, response) 方法完成Web 应用的业务逻辑               
} 
10. Tomcat MyHttpServlet

然而程序到这里还没有完成, 我们先来看一下一个 HttpServletdemo程序

public class MyHttpServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/plain");            // 1. 请求返回的数据的格式
        PrintWriter out = resp.getWriter();           // 2. 获取 new CoyoteWriter(outputBuffer) (PS: outputBuffer是Response里面的内部类, 存储着要写回浏览器的数据)
        Object object = req.getParameter("name");     // 3. 在第一次获取参数时, http body 里面的数据
        HttpSession httpSession = req.getSession();   // 4. 在第一次获取 Session 时会初始化构建Session(PS: 也就是说, 只有在程序里面getSession时才会创建Session)
        httpSession.setAttribute("name", "xjk");      // 5. 在Session里面设置对应 KV 数据
        out.print("OK");                              // 6. 将数据写回 Response 的OutputBuffer里面(PS: 只有在Response commit 时才正真的写数据到浏览器里)
    }
}

看到第二步的PrintWriter, 这个类其实是通过 Response里面的outputBuffer构建出的CoyoteWriter(PS: outputBuffer默认最大存储是 8M, 里面存储的是写到浏览器的数据)
req.getParameter("name") 这个看似简单的方法, 其实里面很有搞头
这里的 req 其实是 org.apache.catalina.connector.Request, 在调用方法的第一次会进行请求参数的第一次解析(调用parseParameters来解析body的数据), 对于这个方法, 其实就是解析URI, body的参数(其间会根据contentType分别处理), 见下面的代码

/**
 * Parse request parameters.
 */
protected void parseParameters() {

    parametersParsed = true;

    Parameters parameters = coyoteRequest.getParameters();
    boolean success = false;
    try {
        // Set this every time in case limit has been changed via JMX
        parameters.setLimit(getConnector().getMaxParameterCount());         // 1. 设置http 请求的最大参数个数 (KV 对)

        // getCharacterEncoding() may have been overridden to search for
        // hidden form field containing request encoding
        String enc = getCharacterEncoding();                                // 2. 这里是从 org.apache.coyote.Request 里面拿取对应的编码模式

        boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI(); // 3. 获取解析 URI 的编码
        if (enc != null) {
            parameters.setEncoding(enc);
            if (useBodyEncodingForURI) {
                parameters.setQueryStringEncoding(enc);
            }
        } else {
            parameters.setEncoding                                          // 4. 若未获取编码格式, 则直接使用 "ISO-8859-1" 这种编码格式
                (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
            if (useBodyEncodingForURI) {
                parameters.setQueryStringEncoding
                    (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
            }
        }

        parameters.handleQueryParameters();

        if (usingInputStream || usingReader) {
            success = true;
            return;
        }

        if( !getConnector().isParseBodyMethod(getMethod()) ) {             // 5. 判断这种请求方法类型是否需要解析 http 的 body
            success = true;
            return;
        }

        String contentType = getContentType();
        if (contentType == null) {
            contentType = "";
        }
        int semicolon = contentType.indexOf(';');
        if (semicolon >= 0) {
            contentType = contentType.substring(0, semicolon).trim();
        } else {
            contentType = contentType.trim();
        }

        if ("multipart/form-data".equals(contentType)) {                    // 6. 若http是文件上传, 则解析上传的文件, 直接对 http 请求的数据进行 part 解析
            parseParts(false);
            success = true;
            return;
        }

        if (!("application/x-www-form-urlencoded".equals(contentType))) {
            success = true;
            return;
        }

        int len = getContentLength();                                       // 7. 获取请求数据长度大小

        if (len > 0) {
            int maxPostSize = connector.getMaxPostSize();                   // 8. 若 http 发来的数据大于 connector 能接收的极限, 则 不进行处理请求
            if ((maxPostSize > 0) && (len > maxPostSize)) {
                if (context.getLogger().isDebugEnabled()) {
                    context.getLogger().debug(
                            sm.getString("coyoteRequest.postTooLarge"));
                }
                checkSwallowInput();
                return;
            }
            byte[] formData = null;
            if (len < CACHED_POST_LEN) { // 这里默认是 8M
                if (postData == null) {
                    postData = new byte[CACHED_POST_LEN];
                }
                formData = postData;
            } else {
                formData = new byte[len];
            }
            try {
                if (readPostBody(formData, len) != len) {                   // 9. 读取 body 里面的数据
                    return;
                }
            } catch (IOException e) {
                // Client disconnect
                if (context.getLogger().isDebugEnabled()) {
                    context.getLogger().debug(
                            sm.getString("coyoteRequest.parseParameters"), e);
                }
                return;
            }
            parameters.processParameters(formData, 0, len);                 // 10. 解析处理 body 里面的数据
        } else if ("chunked".equalsIgnoreCase(                              // 11. 若 header 里面的 transfer-encoding 是 chunked
                coyoteRequest.getHeader("transfer-encoding"))) {
            byte[] formData = null;
            try {
                formData = readChunkedPostBody();                           // 12. 这里就是进行 form 表单提交时, 默认的解析 body 里面数据的入口
            } catch (IOException e) {
                // Client disconnect or chunkedPostTooLarge error
                if (context.getLogger().isDebugEnabled()) {
                    context.getLogger().debug(
                            sm.getString("coyoteRequest.parseParameters"), e);
                }
                return;
            }
            if (formData != null) {
                parameters.processParameters(formData, 0, formData.length);
            }
        }
        success = true;
    } finally {
        if (!success) {
            parameters.setParseFailed(true);
        }
    }
}

接着我们再看看 req.getSession(), 见详情

protected Session doGetSession(boolean create) {        // create: 是否创建 StandardSession

    // There cannot be a session if no context has been assigned yet
    if (context == null) {
        return (null);                                  // 1. 检验 StandardContext
    }

    // Return the current session if it exists and is valid
    if ((session != null) && !session.isValid()) {      // 2. 校验 Session 的有效性
        session = null;
    }
    if (session != null) {
        return (session);
    }

    // Return the requested session if it exists and is valid
    Manager manager = null;
    if (context != null) {
        manager = context.getManager();
    }
    if (manager == null)
     {
        return (null);      // Sessions are not supported
    }
    if (requestedSessionId != null) {
        /**
         * 通过 StandardContext 拿到对应的StandardManager, 查找缓存中是否有对应的客户端传递过来的 sessionId
         * 如果有的话, 那么直接 session.access (计数器 + 1), 然后返回
         */
        try {                                            // 3. 通过 managerBase.sessions 获取 Session
            session = manager.findSession(requestedSessionId);  // 4. 通过客户端的 sessionId 从 managerBase.sessions 来获取 Session 对象
        } catch (IOException e) {
            session = null;
        }
        if ((session != null) && !session.isValid()) {   // 5. 判断 session 是否有效
            session = null;
        }
        if (session != null) {
            session.access();                            // 6. session access +1
            return (session);
        }
    }

    // Create a new session if requested and the response is not committed
    if (!create) {
        return (null);                                   // 7. 根据标识是否创建 StandardSession ( false 直接返回)
    }
    if ((context != null) && (response != null) &&
        context.getServletContext().getEffectiveSessionTrackingModes().
                contains(SessionTrackingMode.COOKIE) &&
        response.getResponse().isCommitted()) {
        throw new IllegalStateException
          (sm.getString("coyoteRequest.sessionCreateCommitted"));
    }

    // Attempt to reuse session id if one was submitted in a cookie
    // Do not reuse the session id if it is from a URL, to prevent possible
    // phishing attacks
    // Use the SSL session ID if one is present.
    if (("/".equals(context.getSessionCookiePath())      // 8. 到这里其实是没有找到 session, 直接创建 Session 出来
            && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {
        session = manager.createSession(getRequestedSessionId()); // 9. 从客户端读取 sessionID
    } else {
        session = manager.createSession(null);
    }

    // Creating a new session cookie based on that session
    if ((session != null) && (getContext() != null)
           && getContext().getServletContext().
                   getEffectiveSessionTrackingModes().contains(
                           SessionTrackingMode.COOKIE)) {
        Cookie cookie =
            ApplicationSessionCookieConfig.createSessionCookie( // 10. 根据 sessionId 来创建一个 Cookie
                    context, session.getIdInternal(), isSecure());

        response.addSessionCookieInternal(cookie);              // 11. 最后在响应体中写入 cookie
    }

    if (session == null) {
        return null;
    }

    session.access();                                           // 12. session access 计数器 + 1
    return session;
}

public Session createSession(String sessionId) {

    if ((maxActiveSessions >= 0) &&
            (getActiveSessions() >= maxActiveSessions)) {       // 1. 判断 单节点的 Session 个数是否超过限制
        rejectedSessions++;
        throw new TooManyActiveSessionsException(
                sm.getString("managerBase.createSession.ise"),
                maxActiveSessions);
    }

    // Recycle or create a Session instance
    // 创建一个 空的 session
    Session session = createEmptySession();                     // 2. 创建 Session

    // Initialize the properties of the new session and return it
    // 初始化空 session 的属性
    session.setNew(true);
    session.setValid(true);
    session.setCreationTime(System.currentTimeMillis());
    session.setMaxInactiveInterval(this.maxInactiveInterval);   // 3. StandardSession 最大的默认 Session 激活时间
    String id = sessionId;
    if (id == null) {
        id = generateSessionId();                               // 4. 生成 sessionId (这里通过随机数来生成)
    }
    session.setId(id);
    sessionCounter++;

    SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
    synchronized (sessionCreationTiming) {
        sessionCreationTiming.add(timing);                      // 5. 每次创建 Session 都会创建一个 SessionTiming, 并且 push 到 链表 sessionCreationTiming 的最后
        sessionCreationTiming.poll();                           // 6. 并且将 链表 最前面的节点删除
    }
    return (session);
}
10. Tomcat 回写数据

Tomcat 会写数据是通过 org.apache.catalina.connectorResponse.finishResponse方法, 这里的逻辑比较多: 涉及两层封装, 写数据时的 commit与Flush 都是通过回调 Http11Processor.InternalOutputBuffer 来完成的; 先看一下整个流程的主要成员:

参与角色
org.apache.catalina.connector.Response                           : 这个 Servlet 容器中封装的 Response 对象
org.apache.catalina.connector.Response.OutputBuffer              : Response 对象中的输出缓冲
org.apache.catalina.connector.Response.OutputBuffer.CharChunk    : Response 对象中输出缓冲下的字符缓冲区域
org.apache.catalina.connector.Response.OutputBuffer.ByteChunk    : Response 对象中输出缓冲下的字节缓冲区域
org.apache.coyote.Response                                       : Tomcat 网络框架封装的 Response
org.apache.coyote.http11;Http11Processor                         : Tomcat 工作线程池执行的任务
org.apache.coyote.http11;InternalOutputBuffer                    : InternalOutputBuffer 通常存在于 Http11Processor 里面, 主要是做最终数据的缓存与刷数据到远端
org.apache.coyote.http11;InternalOutputBuffer.socketBuffer       : 用于存储刷新到远端数据的缓存(这里buffer里面存储的是 header + body 的所有数据)
org.apache.coyote.http11;InternalOutputBuffer.outputStream       : outputStream 是从代表 客户端的 socket 中拿出来的, 最后也是通过这个 Stream 刷数据到远端

先看一张UML 图吧

Response.finishResponse.png

额, 好像都点复杂, 没事, 我们一步一步来;
看代码: org.apache.catalina.connector.OutputBuffer.close() 方法

public void close()
    throws IOException {

    if (closed) {
        return;
    }
    if (suspended) {
        return;
    }

    // If there are chars, flush all of them to the byte buffer now as bytes are used to
    // calculate the content-length (if everything fits into the byte buffer, of course).
    if (cb.getLength() > 0) {                   // 1. 这里 cb 里面的数据就是 Response.getPrintWriter.write("OK") 写到 CharChunk 里面
        cb.flushBuffer();                       // 2. 将 CharChunk 里面的数据刷到 OutputBuffer中 (PS: 这里就是将 CharChunk 里面的数据刷到 ByteChunk 里面), 期间转化会经过字符转化器
    }

    if ((!coyoteResponse.isCommitted())         // 3. 这里的 coyoteResponse 是 coyote 的 response
        && (coyoteResponse.getContentLengthLong() == -1)) {
        // If this didn't cause a commit of the response, the final content
        // length can be calculated
        if (!coyoteResponse.isCommitted()) {    // 4. 设置 Http header 里面 content-length 的长度 (也就是你在 HttpServlet 里面调用CoyoteWriter.print/write 写入的数据的大小)
            coyoteResponse.setContentLength(bb.getLength());
        }
    }
                                                // 5. 下面的 doFlush 其实就是将 org.apache.catalina.connector.Response.OutputBuffer 中的 CharChunk, ByteChunk 的数据(body中的数据, 也就是一开始在 MyHttpServlet中 PrintWriter.write("OK")的数据刷到 Http11Processor.InternalOutputBuffer里面, 当然里面还涉及到 header 中的数据
    if (coyoteResponse.getStatus() ==
            HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
        doFlush(true);
    } else {
        doFlush(false);
    }
    closed = true;

    // The request should have been completely read by the time the response
    // is closed. Further reads of the input a) are pointless and b) really
    // confuse AJP (bug 50189) so close the input buffer to prevent them.
    Request req = (Request) coyoteResponse.getRequest().getNote(
            CoyoteAdapter.ADAPTER_NOTES);
    req.inputBuffer.close();

    coyoteResponse.finish();                   // 6. 这边才是正真写出数据的地方 注意这里是 org.apache.coyote.Response, 将 InternalOutputBuffer 中的 socketBuffer 刷到浏览器中
}

其主要做了下面几步:

1. 将 org.apache.catalina.connector.Response.CharChunk 里面的数据刷到 org.apache.catalina.connector.Response.ByteChunk 里面
2. 将 org.apache.catalina.connector.Response.OutputBuffer 中的 CharChunk, ByteChunk 的数据(body中的数据, 也就是一开始在 MyHttpServlet中 PrintWriter.write("OK")的数据刷到 Http11Processor.InternalOutputBuffer里面, 当然里面还涉及到 header 中的数据
3. 将 InternalOutputBuffer 中的 socketBuffer 刷到浏览器中

其中比较重要的就是 doFlush方法了, 它主要做了下面几步:

1. 调用 Http11Processor.prepareResponse将 header里面的数据刷到 Http11Processor.InternalOutputBuffer里面(先到 headerbuffer, 后到 socketBuffer)
2. 通过 org.apache.catalina.connector.Response.OutputBuffer.ByteBuffer.flushBuffer() 将数据刷到 Http11Processor.InternalOutputBuffer.socketBuffer 里面

见代码:

protected void doFlush(boolean realFlush)
    throws IOException {

    if (suspended) {
        return;
    }

    try {                                             // 前置, 这里做的就两部 1. 调用 Http11Processor.prepareResponse将 header里面的数据刷到 Http11Processor.InternalOutputBuffer里面(先到 headerbuffer, 后到 socketBuffer), 2. 通过 org.apache.catalina.connector.Response.OutputBuffer.ByteBuffer.flushBuffer() 将数据刷到 Http11Processor.InternalOutputBuffer.socketBuffer 里面
        doFlush = true;
        if (initial) {                               // 1. 回调 Http11Processor  (将 header 中要写的数据 commit 到 Http11Processor.InternalOutputBuffer 里面)
            coyoteResponse.sendHeaders();            // 2. coyoteResponse 就是 org.apache.coyote.Response (将 Http header 里面的信息 刷到 headBuffer 中, 然后刷到 socketBuffer 中, 这里的 headBuffer 与 sendBuffer 都是在 InternalOutputBuffer 中)
            initial = false;
        }
        if (cb.getLength() > 0) {
            cb.flushBuffer();
        }
        if (bb.getLength() > 0) {                    // 3. 这里的 bb(ByteChunk) 存储的是 http 请求的 body 里面的数据
            bb.flushBuffer();                        // 4. bb(ByteChunk) 将自己的数据刷到 org.apache.catalina.connector.OutputBuffer 的 outputChunk, 然后再调用 coyoteResponse.doWrite 刷到 InternalOutputBuffer.socketBuffer 里面(这一步经过很多步)
        }
    } finally {
        doFlush = false;
    }

    if (realFlush) {
        coyoteResponse.action(ActionCode.CLIENT_FLUSH,
                              coyoteResponse);
        // If some exception occurred earlier, or if some IOE occurred
        // here, notify the servlet with an IOE
        if (coyoteResponse.isExceptionPresent()) {
            throw new ClientAbortException
                (coyoteResponse.getErrorException());
        }
    }
}

flush其中第一步就是 coyoteResponse.sendHeaders, 这个方法会触发 Http11Processor的action方法, 然后就将Header里面的数据刷到 SocketBuffer里面

prepareResponse();              // 将 Http header 里面的 请求结果状态, header头部的信息刷到 InternalOutput.headerbuffer 里面, 并且根据头部的信息选择合适的 OutputFilter
getOutputBuffer().commit();     // 将 headerbuffer 里面的数据刷到 socketBuffer (socketBuffer 是一个 ByteChunk)

最后就是通过 coyoteResponse.finish() 来触发Http11Processor的action动作, 它其实就是通过 socketBuffer.flushBuffer 来刷数据到远端(浏览器)

11. 总结

整个"一次请求"的过程涉及

1. 网络框架层(coyote)
2. 路由层(Mapper)
3. 容器链路层(Engine, Host, Context, Wrapper, ApplicationFilterChain, Servlet)
4. 数据回写(各中 OutputBuffer, InternalOutputBuffer, PS: 里面有些许操作是通过回调)

对于我们个人来说, 我们可以仿照Tomcat的架构层级而设计一个网络框架(PS: 其实完全可以在现在代码的基础上添加一个协议, 做成 一个IM服务也很容易)

参考:
Tomcat源码阅读之底层 IO 封装(1)InternalNioInputBuffer的分析
Tomcat 5 源码分析
Tomcat 7.0 源码分析——请求原理分析
Tomcat7 源码解析
Tomcat源码分析(二)------ 一次完整请求的里里外外

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

推荐阅读更多精彩内容