目录
前面几篇文章讲解了Tomcat如何启动的,如何通过NIO,开启一个ServerSocket服务监听。
最后用户通过浏览器就可以直接访问到想要的资源。
下图中Connector部分:
一个Connector将在某个指定端口上侦听客户请求,并将获得的请求交给Engine来处理,从Engine处获得回应并返回客户。 Tomcat有两个典型的Connector: - 一个直接侦听来自browser的http请求 - 一个侦听来自其它WebServer的请求 Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求。 Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。
请求到达tomcat 的执行流程
请求如何到达Tomcat:
1.我们发起一个浏览器的请求。
2.请求会被之前tomcat启动后的ServerSocket监听到(这是通过TCP/UDP传输协议实现),
Connector将请求交给Engin ---> Host --->Context 一层层过滤最后到达具体的Servlet。
3.Servlet处理完业务最后将请求返回给Connector,返回给用户。
在进入容器 后如何执行的
将上图进行展开来看一下 :
Tomcat中的六个容器:
容器 | 作用 |
---|---|
Server容器 | 一个StandardServer类实例就表示一个Server容器,server是tomcat的顶级构成容器 |
Service容器 | 一个StandardService类实例就表示一个Service容器,Tomcat的次顶级容器,Service是这样一个集合:它由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求。 |
Engine容器 | 一个StandardEngine类实例就表示一个Engine容器。Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名。当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理 |
Host容器 | 一个StandardHost类实例就表示一个Host容器,代表一个VirtualHost,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配。每个虚拟主机下都可以部署(deploy)一个或者多个WebApp,每个Web App对应于一个Context,有一个Context path。当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。匹配的方法是“最长匹配”,所以一个path==”“的Context将成为该Host的默认Context。所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配 |
Context容器 | 一个StandardContext类实例就表示一个Context容器。一个Context对应于一个Web Application,一个WebApplication由一个或者多个Servlet组成。Context在创建的时候将根据配置文件CATALINA_HOME/conf/web.xml和WEBAPP_HOME/WEB-INF/web.xml载入Servlet类。当Context获得请求时,将在自己的映射表(mappingtable)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回 |
Wrapper容器 | 一个StandardWrapper类实例就表示一个Wrapper容器,Wrapper容器负责管理一个Servlet,包括Servlet的装载、初始化、资源回收。Wrapper是最底层的容器,其不能在添加子容器了。Wrapper是一个接口,其标准实现类是StandardWrapper |
Container用于封装和管理Servlet,以及具体处理Request请求,在Connector内部包含了4个子容器,结构图如下
四个子容器的作用分别是:
- (1)Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;
- (2)Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;
- (3)Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件;
- (4)Wrapper:每一Wrapper封装着一个Servlet。
类中的执行流程:
四个主要容器的执行流程:
Container处理请求是使用Pipeline-Valve管道来处理的!(Valve是阀门之意)
Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的请求返回,再让下一个处理着继续处理。
代码从 NioEndpoint开始
1. 前面的文章讲过如何启动了一个NIO的server
U socket = null;
try {
// Accept the next incoming connection from the server
// socket
socket = endpoint.serverSocketAccept();.............
2. 启动了server后,监听来自浏览器请求
当请求到达tomcat后 ,做为Socket接收者Acceptor会接受所有的请求,最为Socket轮询者Poller,Poller 会负责轮询上述产生的事件,将就绪的事件生成 SokcetProcessor ,交给Excutor去执行。.
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch
// any active event.
// 遍历SelectionKey,并调度活动事件,这个过程跟普通的nio类似
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
// 获取NioSocketWrapper,如果获取为空,说明其他线程调用了cancelKey()
// 则去除这个Key,否则调用processKey()
NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (attachment == null) {
iterator.remove();
} else {
iterator.remove();
LogPropertiesTest.debug(" -------------- 18、Poller : 执行 processKey(sk, attachment); 方法, 执行类 :"+this.getClass());
processKey(sk, attachment); // 处理SelectKey 重要方法 !!!!!!!
}
}//while
我们可以看到这一行代码
processKey(sk, attachment); // 处理SelectKey 重要方法 !!!!!!!
此处遍历SelectionKey,并调度活动事件,这个过程跟普通的nio类似,开始处理一个SelectionKeyprocessKey()这个方法主要通过调用processSocket()方法创建一个SocketProcessor,然后丢到Tomcat线程池中去执行。每个Endpoint都有自己的SocketProcessor实现,
从Endpoint的属性中可以看到,这个Processor也有缓存机制。
总结一下Poller所做的事:遍历PollerEvents队列,将每个事件中的通道感兴趣的事件注册到Selector,当事件就绪时,创建一个SocketProcessor或者从缓存中取出一个SocketProcessor,
然后放到线程池执行或者直接执行它的run方法执行。
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
try {
if ( close ) {
cancelledKey(sk);
} else if ( sk.isValid() && attachment != null ) {
if (sk.isReadable() || sk.isWritable() ) {
if ( attachment.getSendfileData() != null ) {
processSendfile(sk,attachment, false);
} else {
unreg(sk, attachment, sk.readyOps());
boolean closeSocket = false;
// Read goes before write
if (sk.isReadable()) {
//此处判断是否可读
if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
if (!closeSocket && sk.isWritable()) {
if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
if (closeSocket) {
cancelledKey(sk);
}
}
}
} else {
//invalid key
cancelledKey(sk);
}
} catch ( CancelledKeyException ckx ) {
cancelledKey(sk);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("",t);
}
}
看到此行:
if (!processSocket(attachment, SocketEvent.OPEN_READ, true))
调用了父类AbstractEndpoint方法:
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
SocketProcessorBase<S> 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;
}
最终进入 SocketProcessor 的doRun()方法:
代码中已经给了注解解释。
@Override
protected void doRun() {
NioChannel socket = socketWrapper.getSocket();
SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
try {
// 这里的 handshake 是用来标记https的握手完成情况,
// 如果是http不需要该握手阶段,在从 11111111111 处直接置为0
int handshake = -1;
try {
if (key != null) {
if (socket.isHandshakeComplete()) { // 1111111111
// 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.
// 如果不能完成TLS握手过程,标记握手失败
handshake = -1;
} else {
// 处理https的SecureNioChannel覆写了该hanshake()方法
// 返回注册的SelectionKey
handshake = socket.handshake(key.isReadable(), key.isWritable());
// 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; // 标记Socket状态
// Process the request from this socket
// 处理该Sockt里的请求
if (event == null) {
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
if (state == SocketState.CLOSED) {
close(socket, key); // 否则关闭通道
}
} else if (handshake == -1 ) { // 握手失败则关闭通道
close(socket, key);
} else if (handshake == SelectionKey.OP_READ){ // TLS会走到这里
socketWrapper.registerReadInterest(); // 注册读就绪
} else if (handshake == SelectionKey.OP_WRITE){
socketWrapper.registerWriteInterest(); // 注册写就绪
}
} catch (CancelledKeyException cx) {
socket.getPoller().cancelledKey(key);
} catch (VirtualMachineError vme) {
ExceptionUtils.handleThrowable(vme);
} catch (Throwable t) {
log.error("", t);
socket.getPoller().cancelledKey(key);
} finally {
socketWrapper = null;
event = null;
//return to cache
if (running && !paused) {
processorCache.push(this); // 将SocketProcessor放回缓存中
}
}
}
}
简而言之 重要的代码还是看这几行:
// 处理该Sockt里的请求
if (event == null) {
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
方法再次跳转进入了
@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("abstractConnectionHandler.process",
wrapper.getSocket(), status));
}
..........
do {
//此处是重点方法调用
state = processor.process(wrapper, status);
.........
}
.......
}
随后进入了 AbstractProcessorLight 类的 process(SocketWrapperBase<?> socketWrapper, SocketEvent status) 方法。
后再经过调用便进入
CoyoteAdapter类的service方法调用
此方法创建了Request、 Response,并且对Request进行了解析。
@Override
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);
if (request == null) {
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);
// Link objects
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().setQueryStringCharset(connector.getURICharset());
}
if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean async = false;
boolean postParseSuccess = false;
req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
try {
// 解析 Request
// Parse and set Catalina and configuration specific
// request parameters
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) { //解析成功后进入 Sevlet的调用过程。
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
if (request.isAsync()) {
async = true;
ReadListener readListener = req.getReadListener();
if (readListener != null && request.isFinished()) {
// Possible the all data may have been read during service()
// method so this needs to be checked here
ClassLoader oldCL = null;
try {
oldCL = request.getContext().bind(false, null);
if (req.sendAllDataReadEvent()) {
req.getReadListener().onAllDataRead();
}
} finally {
request.getContext().unbind(false, oldCL);
}
}
Throwable throwable =
(Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// If an async request was started, is not going to end once
// this container thread finishes and an error occurred, trigger
// the async error process
if (!request.isAsyncCompleting() && throwable != null) {
request.getAsyncContextInternal().setErrorState(throwable, true);
}
} else {
request.finishRequest();
response.finishResponse();
}
} catch (IOException e) {
// Ignore
} finally {
AtomicBoolean error = new AtomicBoolean(false);
res.action(ActionCode.IS_ERROR, error);
if (request.isAsyncCompleting() && error.get()) {
// Connection will be forcibly closed which will prevent
// completion happening at the usual point. Need to trigger
// call to onComplete() here.
res.action(ActionCode.ASYNC_POST_PROCESS, null);
async = false;
}
// Access log
if (!async && postParseSuccess) {
// Log only if processing was invoked.
// If postParseRequest() failed, it has already logged it.
Context context = request.getContext();
Host host = request.getHost();
// If the context is null, it is likely that the endpoint was
// shutdown, this connection closed and the request recycled in
// a different thread. That thread will have updated the access
// log so it is OK not to update the access log here in that
// case.
// The other possibility is that an error occurred early in
// processing and the request could not be mapped to a Context.
// Log via the host or engine in that case.
long time = System.currentTimeMillis() - req.getStartTime();
if (context != null) {
context.logAccess(request, response, time, false);
} else if (response.isError()) {
if (host != null) {
host.logAccess(request, response, time, false);
} else {
connector.getService().getContainer().logAccess(
request, response, time, false);
}
}
}
req.getRequestProcessor().setWorkerThreadName(null);
// Recycle the wrapper request and response
if (!async) {
updateWrapperErrorCount(request, response);
request.recycle();
response.recycle();
}
}
}
解析了Request中的一些信息后,并且解析成功则进入下面方法的调用:
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
此处便开始了 四个主要容器的执行流程 。
具体如何通过容器 一步步 进入SpringMVC 完成业务代码的,下篇文章单独讲解吧。