Tomcat中Acceptor线程run方法工作流程
1. Acceptor线程run方法工作流程概述:
(1). 把Socket请求封装成一个PollerEvent事件
(2). 添加到Poller线程中的同步队列SynchronizedQueue中,使用了典型的生产者-消费者模式,通过共享资源实现线程之间的通讯。
2. Acceptor线程什么启动?
Acceptor启动调用链如下:
Tomcat容器一键式启动,在启动Connector连接器时,会先去启动Endpoint,NioEndpoint.startInternal()方法调用AbstractEndpoint.startAcceptorThread()方法,触发Acceptor线程的start()方法,Acceptor线程启动。
startAcceptorThread:1304, AbstractEndpoint{org.apache.tomcat.util.net}代码具体如下:
protected void startAcceptorThread() {
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
// NORM_PRIORITY = 5;Thread默认提供了3种线程优先级(1、5、10),Acceptor使用正常线程优先级:5
t.setPriority(getAcceptorThreadPriority());
// daemon = true;设置Acceptor线程为守护线程
t.setDaemon(getDaemon());
t.start();
}
3. Acceptor.run()方法调用链源码跟踪
调用链接如下:
3.1 Acceptor.run()
run:126, Acceptor{org.apache.tomcat.util.net}代码如下:
@Override
public void run() {
int errorDelay = 0;
try {
// Loop until we receive a shutdown command
// 该线程会一直进行while循环,除非收到了shutdown命令,
// 但实际上由于下面ServerSocketChannel默认构造是blocking模式,accept()方法对于blocking模式的channel,
// 策略:一直阻塞,直到可以获得一个新的连接,或者发生了I/O错误。
// 所以当没有连接请求来时,线程会堵塞在accept方法上。
while (!stopCalled) {
// Loop if endpoint is paused
while (endpoint.isPaused() && !stopCalled) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (stopCalled) {
break;
}
state = AcceptorState.RUNNING;
try {
// if we have reached max connections, wait
// 当有新的请求到达时,endpint使用自定义的org.apache.tomcat.util.threads.LimitLatch来管理连接数
// 如果连接数大于最大值,等待,否则LimitLatch的count值加1。最大值为:maxConnections = 8*1024;
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
// 从server socket接受下一次进来的连接
// serverSocketAccept()方法逻辑是通过ServerSocketChannel.accept()监听连接请求,
// 由于ServerSocketChannel使用blocking模式,所以当没有连接请求来时,线程会堵塞在accept方法上。
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
// setSocketOptions()方法如果调用成功了,会把socket传递给合适的processor
if (!endpoint.setSocketOptions(socket)) {
// 传递完之后endpint,关闭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;
}
Acceptor类实现了Runnable接口,重写了run方法,方法逻辑大致如下:
1). while循环对stopCalled值做判断。
2). 当有新的请求到达时,endpint使用自定义的org.apache.tomcat.util.threads.LimitLatch来管理连接数,如果连接数大于最大值,等待,否则LimitLatch的count值加1。
3). 通过ServerSocketChannel.accept()监听连接请求,当没有连接请求来时,线程会堵塞在accept方法上。
4). 调用setSocketOptions()方法把socket传递给合适的processor。
3.2 NioEndpoint.setSocketOptions(SocketChannel socket)
setSocketOptions:535, NioEndpoint{org.apache.tomcat.util.net}代码如下:
/**
* Process the specified connection.
* 执行指定的连接
* @param socket The socket channel
* @return <code>true</code> if the socket was correctly configured
* and processing may continue, <code>false</code> if the socket needs to be
* close immediately
*/
@Override
protected boolean setSocketOptions(SocketChannel socket) {
NioSocketWrapper socketWrapper = null;
try {
// Allocate channel and wrapper
// 初始化channel和wrapper,填充属性,实例化,poller线程后面会处理
NioChannel channel = null;
if (nioChannels != null) {
// private SynchronizedStack<NioChannel> nioChannels; 从同步栈中弹出一个channel
channel = nioChannels.pop();
}
if (channel == null) {
// 预分配读缓冲区、写缓冲区、直接缓冲区
SocketBufferHandler bufhandler = new SocketBufferHandler(
socketProperties.getAppReadBufSize(),
socketProperties.getAppWriteBufSize(),
socketProperties.getDirectBuffer());
if (isSSLEnabled()) {
channel = new SecureNioChannel(bufhandler, selectorPool, this);
} else {
// 构造NioChannel实例,把SocketBufferHandler赋值给NioChannel属性
channel = new NioChannel(bufhandler);
}
}
// 构造NioSocketWrapper
NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);
// 把socket赋值给构造的NioChannnel中SocketChannel
channel.reset(socket, newWrapper);
// map映射
connections.put(socket, newWrapper);
socketWrapper = newWrapper;
// Set socket properties
// Disable blocking, polling will be used
// socket配置成阻塞模式
socket.configureBlocking(false);
if (getUnixDomainSocketPath() == null) {
socketProperties.setProperties(socket.socket());
}
socketWrapper.setReadTimeout(getConnectionTimeout());
socketWrapper.setWriteTimeout(getConnectionTimeout());
socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
// 把封装好的NioSocketWrapper注册到Poller线程中同步队列中
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;
}
NioSocketWrapper() 方法逻辑大致如下:
(1). 初始化channel和wrapper,填充属性,对读缓冲区、写缓冲区、直接缓冲区进行预分配,进行实例化
(2). 把SocketChannel信息和预分配的数据,封装到NioSocketWrapper对象中
(3). 调用Poller线程提供的register方法
3.3 NioEndpoint$Poller.register(final NioSocketWrapper socketWrapper)
register: 771, NioEndpoint$Poller{org.apache.tomcat.util.net}代码如下:
/**
* Registers a newly created socket with the poller.
*
* @param socketWrapper The socket wrapper
*/
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);
}
register()方法如下:
(1). 把NioSocketWrapper封装到PollerEvent对象中
(2). 调用Poller线程提供的addEvent方法
3.4 NioEndpoint$Poller.addEvent(PollerEvent event)
addEvent:667,NioEndpoint$Poller{org.apache.tomcat.util.net}代码如下:
private void addEvent(PollerEvent event) {
events.offer(event);
if (wakeupCounter.incrementAndGet() == 0) {
selector.wakeup();
}
}
addEvent()逻辑如下:
(1). 添加到PollerEvent中的同步队列顶部
(2). 如果selector数量为0,唤醒selector
4. 涉及到的其他类
SocketChannel
java.nio.channels.SocketChannel