当前端发起一次请求,我想了解tomcat是如何处理的。这里写一些文章做一些简短的记录,方便后续复习。
Connector的创建:当实例化一个Connector,构造器函数会通过反射的方式创建一个ProtocolHandler。这里的protocolHandlerClassName实际上是:"org.apache.coyote.http11.Http11NioProtocol";
public Connector(String protocol) {
setProtocol(protocol);
// Instantiate protocol handler
ProtocolHandler p = null;
try {
Class> clazz = Class.forName(protocolHandlerClassName);
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {
this.protocolHandler = p;
}
...
}
为什么要在创建Connector的时候绑定一个ProtocolHandler呢?其实道理很简单,当客户端和服务器发送网络请求时,必须约定协议,tomcat常常作为web端服务器,因此默认提供处理http协议的ProtocolHandler。
而在创建Http11NioProtocol过程中,又创建了下面的内容:
public Http11NioProtocol() {
// 创建一个Nio模型的端点
super(new NioEndpoint());
}
public AbstractHttp11Protocol(AbstractEndpoint endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
// 创建一个ConnectionHandler来处理链接
ConnectionHandler cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}
Connector的初始化:
Tomcat核心的组件都实现了Lifecycle接口,被管理的接口,大概都是经历
init()->start()->stop()的生命周期。
tomcat为了统一管理Lifecycle的组件,提供了抽象类LifecycleBase(实现了Lifecycle接口方法),每一个继承LifecycleBase的组件都会有一个状态,它的默认值是:
private volatile LifecycleState state = LifecycleState.NEW;
下面是LifecycleBase提供init()的模板方法:
@Override
public final synchronized void init() throws LifecycleException {
// 对于任何一个组件来说,一定是在新生状态才能够被初始化,如果不是则抛出异常
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
// init前的状态
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 真正的初始化方法,每个子类必须实现它
initInternal();
// init后的状态
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
这个方法有两个要学习的地方:
第一在方法上使用了synchronized+状态判断,比较简单的方式就保证一个了组件,只能初始化。而状态的使用也是很有用的,当我们有一堆组件,组合成某种功能时,只有当所有组件都处于某种正常的状态时,才能够提供服务的。
第二是利用了抽象类,统一规范了初始化的流程。这里也可以看出接口和抽象类的区别,接口更像是定义了一些系列协议。而抽象类表达的是,你想要实现某种功能,你的标准流程是什么?
因此Connector继承了LifecycleBase,所以要看Connector在初始化,都干了什么事,就找到对应的initInternal():
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// 指定了适配器
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
....
try {
// Http11NioProtocol init
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
下面看下protocolHandler.init(),它实际上要调用AbstractProtocol的init(),最后调用NioEndpoint的init()-> bindWithCleanup()->bind():
public void init() throws Exception {
if (bindOnInit) {
// 初始化服务器
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
....
}
@Override
public void bind() throws Exception {
// 重点!
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();
}
protected void initServerSocket() throws Exception {
if (getUseInheritedChannel()) {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
} else {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
// 获取地址,绑定服务器端口
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.socket().bind(addr,getAcceptCount());
}
// 服务主线程,主要负责链接的请求,为了控制请求的大小
// 所以这里采用了阻塞模式
serverSock.configureBlocking(true); //mimic(模仿) APR behavior
}
其中getAcceptCount()默认值为:100
在这里我们可以看到,我们平时配置的端口时如何生效的,就在addr中
tomcat中的NIO并不全部都是NIO,对于每个客户端的请求,任然使用的时阻塞模式。
到了这里Connector完成了它的初始化工作,注意并没有启动!
Connector开始运行:这里按照Lifecycle的思想,直接看Connector的startInternal()
@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
// endpoint获取请求后,会交给worker去执行,主线程仅仅是获取请求
// 另外这里采用了非阻塞io,等待链接是阻塞式的,处理其他读写事件是用了非阻塞IO
if (getExecutor() == null) {
createExecutor();
}
// 设置最大的链接数:8192。这里需要注意,这里是如何工作的,
// 由于使用了Latch,因此每来一个链接,maxConnection就减少1.直到为0
// 当为0的时候,不能再接受多的链接请求了
initializeConnectionLatch();
// Start poller thread
poller = new Poller();// 打开选择器,Poller其实是一个Runnable
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();// poller 开始工作。Poller 那么这里去看一下run()
// 开启Acceptor Thread专门负责接收socket链接,注意它并不进行socket处理
startAcceptorThread();
}
}
wokers和Poller在后面说
接着我们看下startAcceptorThread()
protected void startAcceptorThread() {
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
Poller和Acceptor都是线程,因此想要明白它们工作内容,之间看对应的run();
首先是Acceptor:
@Override
public void run() {
int errorDelay = 0;
long pauseStart = 0;
try {
// Loop until we receive a shutdown command
while (!stopCalled) {
// Loop if endpoint is paused.
// There are two likely scenarios here.
// The first scenario is that Tomcat is shutting down. In this
// case - and particularly for the unit tests - we want to exit
// this loop as quickly as possible. The second scenario is a
// genuine pause of the connector. In this case we want to avoid
// excessive CPU usage.
// Therefore, we start with a tight loop but if there isn't a
// rapid transition to stop then sleeps are introduced.
// < 1ms - tight loop
// 1ms to 10ms - 1ms sleep
// > 10ms - 10ms sleep
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
// 处理的socket
if (!endpoint.processSocket(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 org.apache.tomcat.jni.Error) {
org.apache.tomcat.jni.Error e = (org.apache.tomcat.jni.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;
}
Acceptor作为一个线程一直while循环,直到stop。通过代码也大概看得出
,它的主要工作就是负责接受请求。
在请求到来后,Acceptor会分派给Poller去处理。那么有一个问题是,如果不限制请求量大小,服务器会崩溃,那么接着就会产生一个疑问,既然tomcat有并发限制,为什么我们还要对接口做性能调优?
在这个方法中,有一行很重要的代码endpoint.processSocket(socket)。最初源代码叫endpoint.setSocketOptions(socket),我为了阅读方便,调整成processSocket,后面整体理解了下processSocket确实不太好,因为Acceptor仅仅负责接受请求,它并不处理请求。
由于现在仅仅是start,并没有请求到来,因此线程被阻塞在socket = endpoint.serverSocketAccept();这个方法实际上是:
protected SocketChannel serverSocketAccept() throws Exception {
// 由于是阻塞模式,因此没有新的请求到来时,线程被阻塞在这里了
SocketChannel result = serverSock.accept();
return result;
}
好了,到了这里,Connector已经启动好了,可以接受客户端的请求了
Poller呢?由于它和链接的处理,非常紧密,在后续请求到来时说。
Connector的启动主要目的就是提供好服务端的serverSocket,指定好当有socket请求时,要按照协议去处理请求(Http11NioProtocol)