一、逻辑分析:
连接器中有三个核心组件:Endpoint、Processor 和 Adapter, 其中Endpoint 和 Processor 放在一起抽象成了 ProtocolHandler 组件, 关系图如下:
- Endpoint
Endpoint 是通信端点,即通信监听的接口,是具体的 Socket 接收和发送处理器,是对传输层的抽象,因此 Endpoint 是用来实现 TCP/IP 协议的。
Endpoint 是一个接口,对应的抽象实现类是 AbstractEndpoint,而 AbstractEndpoint 的具体子类,比如在 NioEndpoint 和 Nio2Endpoint 中,有两个重要的子组件:Acceptor 和 SocketProcessor。
其中 Acceptor 用于监听 Socket 连接请求。SocketProcessor 用于处理接收到的 Socket 请求,它实现 Runnable 接口,在 run 方法里调用协议处理组件 Processor 进行处理。为了提高处理能力,SocketProcessor 被提交到线程池来执行。而这个线程池叫作执行器(Executor),我在后面的专栏会详细介绍 Tomcat 如何扩展原生的 Java 线程池。
- Processor
如果说 Endpoint 是用来实现 TCP/IP 协议的,那么 Processor 用来实现 HTTP 协议,Processor 接收来自 Endpoint 的 Socket,读取字节流解析成 Tomcat Request 和 Response 对象,并通过 Adapter 将其提交到容器处理,Processor 是对应用层协议的抽象。
Processor 是一个接口,定义了请求的处理等方法。它的抽象实现类 AbstractProcessor 对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有 AjpProcessor、Http11Processor 等,这些具体实现类实现了特定协议的解析方法和请求处理方式。
- Adapter
由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat 定义了自己的 Request 类来 “存放” 这些请求信息。ProtocolHandler 接口负责解析请求并生成 Tomcat Request 类。但是这个 Request 对象不是标准的 ServletRequest,也就意味着,不能用 Tomcat Request 作为参数来调用容器。Tomcat 设计者的解决方案是引入 CoyoteAdapter,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的 sevice 方法,传入的是 Tomcat Request 对象,CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest,再调用容器的 service 方法。
连接器的组件图:
Endpoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 run 方法会调用 Processor 组件去解析应用层协议,Processor 通过解析生成 Request 对象后,会调用 Adapter 的 Service 方法。
二、源码分析:
Connector#startInternal()方法实现
@Override
protected void startInternal() throws LifecycleException {
//端口验证
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}
setState(LifecycleState.STARTING);
try {
/**
* 启动ProtocolHandler, Connector使用ProtocolHandler来处理请求,不同的ProtocolHandler代表不同的连接类型
*
* protocolHandler ==> {@link org.apache.coyote.http11.Http11NioProtocol}
*
* {@link AbstractProtocol#start()}
*/
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
连接器中Endpoint 和 Processor 放在一起抽象成了 ProtocolHandler 组件, 因此此处调用AbstractProtocol#start()启动连接器
由于 I/O 模型和应用层协议可以自由组合,比如 NIO + HTTP 或者 NIO.2 + AJP。Tomcat 的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫 ProtocolHandler 的接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类。比如:Http11NioProtocol 和 AjpNioProtocol。除了这些变化点,系统也存在一些相对稳定的部分,因此 Tomcat 设计了一系列抽象基类来封装这些稳定的部分,抽象基类 AbstractProtocol 实现了 ProtocolHandler 接口。每一种应用层协议有自己的抽象基类,比如 AbstractAjpProtocol 和 AbstractHttp11Protocol,具体协议的实现类扩展了协议层抽象基类。继承关系如下:
AbstractProtocol#start()方法实现:
@Override
public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
}
/**
* {@link AbstractEndpoint#start()}
*/
endpoint.start();
//启动超时线程, 用来处理请求超时的连接
asyncTimeout = new AsyncTimeout();
Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
int priority = endpoint.getThreadPriority();
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
priority = Thread.NORM_PRIORITY;
}
timeoutThread.setPriority(priority);
timeoutThread.setDaemon(true);
/**
* {@link AsyncTimeout#run()}
*/
timeoutThread.start();
}
分析:
- 启动Endpoint
- 启动超时线程, 用来处理请求超时的连接
AbstractEndpoint#start()方法实现:
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) { //在初始化init()方法中已经绑定端口, 此处不再执行
bind();
bindState = BindState.BOUND_ON_START;
}
/**
* {@link NioEndpoint#startInternal()}
*/
startInternal();
}
分析:
- 如果没有绑定端口, 则调用bind()方法绑定 (在初始化Endpoint时会绑定地址端口)
- 调用AbstractEndpoint的实现类NioEndpoint的startIniternal()方法进行初始化操作
NioEndpoint#startInternal()方法实现
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
/**
* 创建线程池
* Endpoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,
* SocketProcessor 的 run 方法会调用 Processor 组件去解析应用层协议
*/
if ( getExecutor() == null ) {
createExecutor();
}
/**
* 初始化连接限制
* NIO 模式下默认是 10000,达到这个阈值后,连接请求被拒绝
*/
initializeConnectionLatch();
//创建Poller线程组, 并遍历启动
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
/**
* Acceptor接收到请求后会交给Poller处理, 而poller的处理逻辑在run方法中
* {@link Poller#run()}
*/
pollerThread.start();
}
/**
* 启动Acceptor线程, 开始接收客户端请求
*/
startAcceptorThreads();
}
}
分析:
- 初始化同步栈; processorCache: 缓存SocketProcessor实例; eventCache: 缓存poller事件; nioChannels: 字节缓冲区高速缓存,每个通道保存一组缓冲区(两个,SSL除外,四个)
- 创建线程池; Endpoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 run 方法会调用 Processor 组件去解析应用层协议
- 初始化连接限制; NIO 模式下默认是 10000,达到这个阈值后,连接请求被拒绝
- 创建Poller线程组, 并遍历启动
- 启动Acceptor线程, 开始接收客户端请求
(1) 创建线程池: AbstractEndpoint#createExecutor()方法实现:
public void createExecutor() {
internalExecutor = true;
//创建任务队列
TaskQueue taskqueue = new TaskQueue();
//创建任务线程工厂
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
//创建线程池
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
(2) 初始化连接限制: AbstractEndpoint#initializeConnectionLatch()方法实现:
protected LimitLatch initializeConnectionLatch() {
if (maxConnections==-1) return null;
if (connectionLimitLatch==null) {
connectionLimitLatch = new LimitLatch(getMaxConnections());
}
return connectionLimitLatch;
}
(3) 创建Poller线程组, 并遍历启动: poller线程run方法逻辑实现:
/**
* tomcat的后台poller线程的主逻辑 , 循环处理以下几件事情 :
* 1. 每次循环处理PollerEvent事件队列中所有的事件
* 2. 每次循环处理NIO selector所关注的事件中发生的事件(所有请求的处理,实际上这里都委托给了worker线程)
* 3. 超时处理:每次循环中特定条件满足时执行一次超时处理
* 4. 结束检测:如果被通知结束,执行结束逻辑,也就是该run()方法内的while-loop的结束
*/
@Override
public void run() {
//轮询检测是否有socket可读
while (true) {
// 没有收到停止消息,处理PollerEvent事件队列中所有的事件
boolean hasEvents = false;
try {
/**
* 没有收到停止消息,处理PollerEvent事件队列中所有的事件
*/
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);
}
/**
* 收到结束通知,poller线程停止前先处理掉PollerEvent队列中的事件
*/
if (close) {
events();
// poller关闭前的超时处理
timeout(0, false);
// 结束Java NIO selector,也就是关闭接收和处理服务
try {
selector.close();
} catch (IOException ioe) {
log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
}
// 被通知结束并且处理完收尾工作,现在结束整个线程的while-loop
break;
}
} catch (Throwable x) {
// 出现异常不退出,记日志然后 poller 线程 while-loop继续执行
ExceptionUtils.handleThrowable(x);
log.error("",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;
/**
* 遍历处理所有待处理的NIO事件
*/
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (attachment == null) {
iterator.remove();
} else {
iterator.remove();
/**
* 处理有待处理事件的SelectionKey, 其实真正的处理都委托给了 worker 线程
*/
processKey(sk, attachment);
}
}//while
//process timeouts,正常运行中处理超时
timeout(keyCount,hasEvents);
}//while
getStopLatch().countDown();
}
(4) 启动Acceptor线程, 开始接收客户端请求: AbstractEndpoint#startAcceptorThreads()方法实现
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
/**
* 调用start方法后, 将会执行线程的run()方法
* {@link NioEndpoint.Acceptor#run()}
*/
t.start();
}
}
acceptor线程执行逻辑:
@Override
public void run() {
int errorDelay = 0;
//循环处理接受请求, 直到收到关闭命令
while (running) {
//如果endpoint是暂停状态的, 进入循环
while (paused && running) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
state = AcceptorState.RUNNING;
try {
/**
* LimitLatch:连接控制器,负责控制最大连接数
* NIO 模式下默认是 10000,达到这个阈值后,连接请求被拒绝。
*/
countUpOrAwaitConnection(); // 父类方法
SocketChannel socket = null;
try {
/**
* 获取到客户端发送过来的连接
*/
socket = serverSock.accept();
} catch (IOException ioe) {
//如果出现异常, 没有获取到socket, 则将connectionLimitLatch减一
countDownConnection();
if (running) {
//必要时引入延迟
errorDelay = handleExceptionWithDelay(errorDelay);
throw ioe;
} else {
break;
}
}
//成功接受,重置错误延迟
errorDelay = 0;
// Configure the socket
if (running && !paused) {
/**
* 此处虽然获取到了Socket, 但是用户线程需要等待内核把数据从网卡传输到内核, 再由用户线程从内核的缓冲区获取到相关数据,
* 在此过程中,需要把socket包装为PollerEvent注册到Poller线程中, Poller不断的通过内部的 Selector 对象向内核查询 Channel 的状态,
* 一旦可读就生成任务类 SocketProcessor 交给Executor去处理
*
* 将把socket进行包装移交给合适的处理器
* 如果出现异常, 则返回false,关闭socket
*/
if (!setSocketOptions(socket)) {
closeSocket(socket);
}
} else {
closeSocket(socket);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.accept.fail"), t);
}
}
state = AcceptorState.ENDED;
}
至此, 连接器已启动完成;
相关文章: