前言
因为现在项目都是套框架,让程序员忽略了很多本质的东西,比如一个请求来了它到底怎么运转的,我们在何处可以对它进行加工处理等等,想搞懂这个就需要从源头Tomcat开始,因此就有了这篇文章。
本文是依据Tomcat-9.0.58版本编写,默认使用的是Nio模式。
一、目录结构
- bin: 主要包含可执行程序及脚本
- conf: 配置文件目录,以下是经常会使用到的配置文件
- context.xml: 应用上下文配置文档,多个应用程序可以配置多个,此文件tomcat会定时扫描,因此不用重启tomcat就可以添加新应用.
- server.xml: tomcat服务器配置文档,用以详细配置和使用各组件,如配置service、 connector、Engine、host、context等
- web.xml: web应用程序配置文档,主要配置listener、filter以及servlet等 - lib:依赖包文目录
- logs: 日志存放目录
- temp: 临时文件目录
- webapps: 应用程序存放目录,可直接存放war包,tomcat会自动解压
- work: tomcat工作目录,用来存放解析后的class文件等
注:context.xml是server层面,如果想从host层面进行新增应用的话需要在conf文件夹中新增文件夹,完整路径如下:conf/Catalina/${hostName}/context.xml
二、组件架构
- Server: 服务器,代表整个 Catalina servlet 容器,它的属性就是整个 servlet 容器的特征
- Service: 服务,在Server 中存在一个或多个,内部包含一个或多个Connector和一个Engine,有Service才能够向外提供服务
- Connector: 连接器,在Service中存在一个或者多个,每个连接器中都必须存在端口和协议,连接器的作用是监听端口接受外部连接请求,封装请求交给Engine处理,以及返回Engine处理后的响应结果
- Engine: 引擎也被称为Container,在Service中只能存在一个,它接收并处理来自一个或多个连接器的所有请求,并将完成的响应返回给连接器
- Host: 虚拟主机,类似域名,在Engine中存在一个或多个。
- Context: 上下文,在Host中存在一个或多个,每个Context都代表一个Web 应用程序。
三、工作原理
由组件架构图可知,tomcat 工作的核心组件是service 中的Connector 和Engine ,简单来说就是Connector 监听端口然后读取socket 封装成request,response 交给Engine 处理,Engine 处理完后,Connector 回写流给客户端,下面详细的讲一下这两个重要的组件。
1、Connector
参考 Tomcat官方启动时序图可知,程序入口是Bootstrap中的main方法,下面看看这个main方法:
public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
从代码中可以看出main方法中主要涉及到流程的方法是load() 和 start()。其中load() 方法通过反射会调用Catalina.load() 方法,然后Catalina.load()方法会初始化一个Server出来,并且调用server.init() 方法,然后一层一层往下掉用,start() 方法和load 的方法类似也是一层一层调用下来,为了方便大家看的明白特地画了个时序图。
中间过程就不看了,重点看下NioEndPoint 里面的start() 方法,start() 方法主要是掉用startInternal() 方法.
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
if (socketProperties.getBufferPool() != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}
// Create worker collection
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
startAcceptorThread();
}
}
startInternal() 方法主要做了以下几件事:
- 如果Server.xml 中没有配置线程池的话,此时会初始化一个工作线程池.
- 始化默认最大连接数,
- 创建一个Poller 线程并启动
- 创建一个Acceptor 线程并启动
那么Poller 线程和Acceptor 线程是干啥的呢 ?接着往下看。
public class Acceptor<U> implements Runnable {
.....
@Override
public void run() {
int errorDelay = 0;
long pauseStart = 0;
try {
// Loop until we receive a shutdown command
while (!stopCalled) {
while (endpoint.isPaused() && !stopCalled) {
if (state != AcceptorState.PAUSED) {
pauseStart = System.nanoTime();
// Entered pause state
state = AcceptorState.PAUSED;
}
if ((System.nanoTime() - pauseStart) > 1_000_000) {
// Paused for more than 1ms
try {
if ((System.nanoTime() - pauseStart) > 10_000_000) {
Thread.sleep(10);
} else {
Thread.sleep(1);
}
} catch (InterruptedException e) {
// Ignore
}
}
}
if (stopCalled) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
endpoint.countUpOrAwaitConnection();
// Endpoint might have been paused while waiting for latch
// If that is the case, don't accept new connections
if (endpoint.isPaused()) {
continue;
}
U socket = null;
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 (!stopCalled && !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);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
String msg = sm.getString("endpoint.accept.fail");
// APR specific.
// Could push this down but not sure it is worth the trouble.
if (t instanceof Error) {
Error e = (Error) t;
if (e.getError() == 233) {
// Not an error on HP-UX so log as a warning
// so it can be filtered out on that platform
// See bug 50273
log.warn(msg, t);
} else {
log.error(msg, t);
}
} else {
log.error(msg, t);
}
}
}
} finally {
stopLatch.countDown();
}
state = AcceptorState.ENDED;
}
}
// NioEndpoint
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, 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);
if (getUnixDomainSocketPath() == null) {
socketProperties.setProperties(socket.socket());
}
socketWrapper.setReadTimeout(getConnectionTimeout());
socketWrapper.setWriteTimeout(getConnectionTimeout());
socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
poller.register(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;
}
public void register(final NioSocketWrapper socketWrapper) {
socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
PollerEvent event = null;
if (eventCache != null) {
event = eventCache.pop();
}
if (event == null) {
event = new PollerEvent(socketWrapper, OP_REGISTER);
} else {
event.reset(socketWrapper, OP_REGISTER);
}
addEvent(event);
}
Acceptor 中的 run 方法中调用了两个比较重要的方法,其一就是endpoint.serverSocketAccept() 顾名思义就是接受客户端connection ,然后创建一个针对这个连接的SocketChannel 通道,一个连接一个SocketChannel,后续request 数据和response 数据返回都是通过SocketChannel 进行传输,另外SocketChannel 是双向的,这就意味着可以支持同时读写;第二个就是endpoint.setSocketOptions(socket),它的作用是将SocketChannel 包装放入PollerEvent 然后将OP_REGISTER 事件注册到Poller 中,Poller 则会将这个SocketChannel 注册到select 中 。接着就到Poller 线程了,Poller 线程干了啥呢,接着往下看。
public class Poller implements Runnable {
.......
@Override
public void run() {
// Loop until destroy() is called
while (true) {
boolean hasEvents = false;
try {
if (!close) {
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {
// If we are here, means we have other stuff to do
// Do a non blocking select
keyCount = selector.selectNow();
} else {
keyCount = selector.select(selectorTimeout);
}
wakeupCounter.set(0);
}
if (close) {
events();
timeout(0, false);
try {
selector.close();
} catch (IOException ioe) {
log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
}
break;
}
// Either we timed out or we woke up, process events first
if (keyCount == 0) {
hasEvents = (hasEvents | events());
}
} catch (Throwable x) {
ExceptionUtils.handleThrowable(x);
log.error(sm.getString("endpoint.nio.selectorLoopError"), x);
continue;
}
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();
iterator.remove();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (socketWrapper != null) {
processKey(sk, socketWrapper);
}
}
// Process timeouts
timeout(keyCount,hasEvents);
}
getStopLatch().countDown();
}
protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
try {
if (close) {
cancelledKey(sk, socketWrapper);
} else if (sk.isValid()) {
if (sk.isReadable() || sk.isWritable()) {
if (socketWrapper.getSendfileData() != null) {
processSendfile(sk, socketWrapper, false);
} else {
unreg(sk, socketWrapper, sk.readyOps());
boolean closeSocket = false;
// Read goes before write
if (sk.isReadable()) {
if (socketWrapper.readOperation != null) {
if (!socketWrapper.readOperation.process()) {
closeSocket = true;
}
} else if (socketWrapper.readBlocking) {
synchronized (socketWrapper.readLock) {
socketWrapper.readBlocking = false;
socketWrapper.readLock.notify();
}
} else if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
if (!closeSocket && sk.isWritable()) {
if (socketWrapper.writeOperation != null) {
if (!socketWrapper.writeOperation.process()) {
closeSocket = true;
}
} else if (socketWrapper.writeBlocking) {
synchronized (socketWrapper.writeLock) {
socketWrapper.writeBlocking = false;
socketWrapper.writeLock.notify();
}
} else if (!processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
if (closeSocket) {
cancelledKey(sk, socketWrapper);
}
}
}
} else {
// Invalid key
cancelledKey(sk, socketWrapper);
}
} catch (CancelledKeyException ckx) {
cancelledKey(sk, socketWrapper);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.nio.keyProcessingError"), t);
}
}
}
// AbstractEndpoint
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();
}
} catch (RejectedExecutionException ree) {
getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
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
getLog().error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
Poller 线程是事件驱动的,基于IO多路复用原理,当任意一个SocketChannel 中的数据准备好了就会select 就会遍历所有SocketChannel 找到数据准备好的SocketChannel 走后续流程,否则阻塞。后续流程主要掉用 processKey 方法,它主要的作用是根据socketWrapper和SocketEvent构建一个 processor 线程交给工作线程池执行。SocketProcessor 线程 执行方法如下:
protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
......
@Override
protected void doRun() {
/*
* Do not cache and re-use the value of socketWrapper.getSocket() in
* this method. If the socket closes the value will be updated to
* CLOSED_NIO_CHANNEL and the previous value potentially re-used for
* a new connection. That can result in a stale cached value which
* in turn can result in unintentionally closing currently active
* connections.
*/
Poller poller = NioEndpoint.this.poller;
if (poller == null) {
socketWrapper.close();
return;
}
try {
int handshake = -1;
try {
if (socketWrapper.getSocket().isHandshakeComplete()) {
// No TLS handshaking required. Let the handler
// process this socket / event combination.
handshake = 0;
} else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
event == SocketEvent.ERROR) {
// Unable to complete the TLS handshake. Treat it as
// if the handshake failed.
handshake = -1;
} else {
handshake = socketWrapper.getSocket().handshake(event == SocketEvent.OPEN_READ, event == SocketEvent.OPEN_WRITE);
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
event = SocketEvent.OPEN_READ;
}
} catch (IOException x) {
handshake = -1;
if (log.isDebugEnabled()) {
log.debug("Error during SSL handshake",x);
}
} catch (CancelledKeyException ckx) {
handshake = -1;
}
if (handshake == 0) {
SocketState state = SocketState.OPEN;
// Process the request from this socket
if (event == null) {
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
if (state == SocketState.CLOSED) {
poller.cancelledKey(getSelectionKey(), socketWrapper);
}
} else if (handshake == -1 ) {
getHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL);
poller.cancelledKey(getSelectionKey(), socketWrapper);
} else if (handshake == SelectionKey.OP_READ){
socketWrapper.registerReadInterest();
} else if (handshake == SelectionKey.OP_WRITE){
socketWrapper.registerWriteInterest();
}
} catch (CancelledKeyException cx) {
poller.cancelledKey(getSelectionKey(), socketWrapper);
} catch (VirtualMachineError vme) {
ExceptionUtils.handleThrowable(vme);
} catch (Throwable t) {
log.error(sm.getString("endpoint.processing.fail"), t);
poller.cancelledKey(getSelectionKey(), socketWrapper);
} finally {
socketWrapper = null;
event = null;
//return to cache
if (running && processorCache != null) {
processorCache.push(this);
}
}
}
SocketProcessor 线程主要的作用是 判断SocketChannel 是否读写流准备好了,如果没有准备好就添加事件到Poller 线程中,否则掉用 getHandler().process() 方法,这个方法最终会调用到具体的processor 类中的service 方法如Http11Processor 中的service 方法
public class Http11Processor extends AbstractProcessor {
public SocketState service(SocketWrapperBase<?> socketWrapper){
......
getAdapter().service(request, response);
......
}
}
而Http11Processor 也是中间过程会继续往下调用,此时就到了CoyoteAdapter.service(org.apache.coyote.Request req, org.apache.coyote.Response res) 方法中了
public class CoyoteAdapter implements Adapter {
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)throws Exception {
......
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
......
}
}
CoyoteAdapter 适配器主要的作用是将Http11Processor 中传递过来的request 和response 进行封装,交给Engine 处理,自此在Connector 中前半部分已经结束了,最后来个总结,为了大家看的明白画了个图。
- Acceptor : 单线程,采用SocketChannel 通道原理读取socket ,然后将SocketChannel 封装到PollerEvent对象 ,然后注册事件到Poller 中。Acceptor是事件的生产者。
- Poller : 单线程,事件驱动,基于IO多路复用原理 检查是否通道中流准备好了,准备好了就走后续流程,创建SocketProcessor 线程并交给Work ThreadPool 执行。Poller 是事件的消费者。
- SocketProcessor : 工作线程池调度,作用找到相应的Processor 处理,最终找到CoyoteAdapter 适配器 ,封装request 和response 交给Engine 处理,最后也是由CoyoteAdapter 适配器调用方法将response 流回写到SocketChannel中,这样客户端就可以得到服务器的反馈内容了。
注意:默认是NioChannel ,支持双向即可以同时读写,但是多个读在同一个NioChannel还是是阻塞模式。
2、Engine
Engine 简单来说就是处理请求的,也就是掉用具体servlet 的service 方法.
由上一步可知最后执行到这里:connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); 解释一下这些方法是获取的都是什么。
- getService() : StandardService
- getContainer() : StandardEngine
- getPipeline() : StandardPipeline
- getFirst() : StandardEngineValve
那么代码走着
final class StandardEngineValve extends ValveBase {
.........
@Override
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.
// Don't overwrite an existing error
if (!response.isError()) {
response.sendError(404);
}
return;
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(host.getPipeline().isAsyncSupported());
}
// Ask this Host to process this request
host.getPipeline().getFirst().invoke(request, response);
}
}
后续context、wrapper 步骤都是invoke 因此就不在贴代码了
context.getPipeline().getFirst().invoke(request, response);
wrapper.getPipeline().getFirst().invoke(request, response);
最后重点贴一下StandardWrapperValve.invoke() 方法
//StandardWrapperValve
public final void invoke(Request request, Response response)
throws IOException, ServletException {
.......
// Create the filter chain for this request
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
filterChain.doFilter
(request.getRequest(), response.getResponse());
.......
}
//ApplicationFilterChain
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
StandardWrapperValve.invoke() 方法最终会调用到ApplicationFilterChain.internalDoFilter() 方法。ApplicationFilterChain.internalDoFilter() 方法主要干了两件事
- 过滤链:责任链设计模式从filters 取出传递过滤
- 调用servlet 的sevice(request, response) 方法
处理完成后会一层一层返回至CoyoteAdapter
public class CoyoteAdapter implements Adapter {
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)throws Exception {
......
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
......
request.finishRequest();
response.finishResponse();
......
}
}
CoyoteAdapter 会调用 response.finishResponse() 方法将response流通过SocketChannel 回写给客户端,具体代码就不贴了,有兴趣自己可以查看Tomcat 源码。最后画个图总结一下。
总结
本文只是简单Connector加载过程及请求流转过程,忽略很多不涉及流程的步骤,如需深究请查看源码