在分析Lifecycle接口之后,本文分析Connector組件的初始化和啟動過程。
Connector
與其他組件一樣,Connector類也繼承了LifecycleMBeanBase類,其構(gòu)造函數(shù)和成員變量如下所示:
public class Connector extends LifecycleMBeanBase {
private static final Log log = LogFactory.getLog(Connector.class);
// ------------------------------------------------------------ Constructor
public Connector() {
this(null);
}
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;
}
if (Globals.STRICT_SERVLET_COMPLIANCE) {
uriCharset = StandardCharsets.ISO_8859_1;
} else {
uriCharset = StandardCharsets.UTF_8;
}
}
// ----------------------------------------------------- Instance Variables
/**
* The <code>Service</code> we are associated with (if any).
*/
protected Service service = null;
protected boolean allowTrace = false;
protected long asyncTimeout = 30000;
protected boolean enableLookups = false;
protected boolean xpoweredBy = false;
protected int port = -1;
protected String proxyName = null;
protected int proxyPort = 0;
protected int redirectPort = 443;
protected String scheme = "http";
protected boolean secure = false;
protected static final StringManager sm = StringManager.getManager(Connector.class);
private int maxCookieCount = 200;
protected int maxParameterCount = 10000;
protected int maxPostSize = 2 * 1024 * 1024;
protected int maxSavePostSize = 4 * 1024;
protected String parseBodyMethods = "POST";
protected HashSet<String> parseBodyMethodsSet;
protected boolean useIPVHosts = false;
protected String protocolHandlerClassName ="org.apache.coyote.http11.Http11NioProtocol";
protected final ProtocolHandler protocolHandler;
protected Adapter adapter = null;
@Deprecated
protected String URIEncoding = null;
protected String URIEncodingLower = null;
private Charset uriCharset = StandardCharsets.UTF_8;
protected boolean useBodyEncodingForURI = false;
}
成員變量的含義可以參考Connector配置文檔,以下的屬性值得特別注意:
- URIEncoding屬性是用來對URI百分號編碼解碼時用的編碼,從Tomcat 8開始,URIEncoding屬性的默認(rèn)值是UTF-8;
- useBodyEncodingForURI屬性默認(rèn)為false,若設(shè)置為true那么Tomcat會使用Content-Type頭或ServletRequest接口的setCharacterEncoding方法指定的編碼解析查詢字符串,若編碼不被支持,那么使用默認(rèn)的ISO-8859-1。請注意該屬性只適用于查詢字符串,不適用于URI的路徑部分。
- 以上兩個屬性的解釋也可參考Tomcat Wiki。
初始化Connector
initInternal方法主要做了以下幾件事:
- 創(chuàng)建一個CoyoteAdapter并關(guān)聯(lián)到此Connector上;
- 初始化此Connector的protocolHandler。
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
getProtocolHandlerClassName()));
}
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
(AbstractHttp11JsseProtocol<?>) protocolHandler;
if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// OpenSSL is compatible with the JSSE configuration, so use it if APR is available
jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
}
}
try {
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
啟動Connector
Connector類的startInternal方法啟動了關(guān)聯(lián)的protocolHandler:
@Override
protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}
setState(LifecycleState.STARTING);
try {
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
Http11NioProtocol
本節(jié)以常用的Http11NioProtocol分析ProtocolHandler的初始化和啟動過程,Http11NioProtocol的類層次結(jié)構(gòu)如下圖所示。

Http11NioProtocol對象在被構(gòu)造時,為其自己關(guān)聯(lián)了一個NioEndpoint,類層次結(jié)構(gòu)上的構(gòu)造函數(shù)代碼如下:
public Http11NioProtocol() {
super(new NioEndpoint());
}
public AbstractHttp11JsseProtocol(AbstractJsseEndpoint<S> endpoint) {
super(endpoint);
}
public AbstractHttp11Protocol(AbstractEndpoint<S> endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}
public AbstractProtocol(AbstractEndpoint<S> endpoint) {
this.endpoint = endpoint;
setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
Http11NioProtocol的init和start方法都在其父類AbstractProtocol中定義,部分代碼如下:
public abstract class AbstractProtocol<S> implements ProtocolHandler, MBeanRegistration {
// 省略一些代碼
private final AbstractEndpoint<S> endpoint;
@Override
public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
}
// 省略一些JMX相關(guān)代碼
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
endpoint.init();
}
@Override
public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
}
endpoint.start();
// Start async timeout thread
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);
timeoutThread.start();
}
// 省略一些代碼
}
- 初始化過程除了JMX相關(guān)的代碼就是初始化關(guān)聯(lián)的端點。首先調(diào)用端點的setName方法設(shè)置名稱,然后執(zhí)行初始化工作;
- 啟動過程啟動了關(guān)聯(lián)的端點。
端點名稱與ProtocolHandler實現(xiàn)有關(guān),AbstractProtocol類中的getName、getNameInternal和getNamePrefix是用于獲取ProtocolHandler名稱的函數(shù),代碼如下:
public String getName() {
return ObjectName.quote(getNameInternal());
}
private String getNameInternal() {
StringBuilder name = new StringBuilder(getNamePrefix());
name.append('-');
if (getAddress() != null) {
name.append(getAddress().getHostAddress());
name.append('-');
}
int port = getPort();
if (port == 0) {
// Auto binding is in use. Check if port is known
name.append("auto-");
name.append(getNameIndex());
port = getLocalPort();
if (port != -1) {
name.append('-');
name.append(port);
}
} else {
name.append(port);
}
return name.toString();
}
protected abstract String getNamePrefix();
Http11NioProtocol類實現(xiàn)的getNamePrefix方法如下,所以端點名有類似“http-nio-端口號”這種形式,這在日志輸出時有體現(xiàn),其他ProtocolHandler同理。
@Override
protected String getNamePrefix() {
if (isSSLEnabled()) {
return ("https-" + getSslImplementationShortName()+ "-nio");
} else {
return ("http-nio");
}
}
下面分析端點AbstractEndpoint和實現(xiàn)類NioEndpoint。
AbstractEndpoint
AbstractEndpoint類的層次結(jié)構(gòu)如下圖所示:

AbstractEndpoint類的部分代碼如下:
public abstract class AbstractEndpoint<S> {
// 省略一些代碼
protected volatile boolean running = false;
protected volatile boolean paused = false;
protected volatile boolean internalExecutor = true;
private volatile LimitLatch connectionLimitLatch = null;
protected SocketProperties socketProperties = new SocketProperties();
public SocketProperties getSocketProperties() {
return socketProperties;
}
protected Acceptor[] acceptors;
protected SynchronizedStack<SocketProcessorBase<S>> processorCache;
private int acceptCount = 100;
public void setAcceptCount(int acceptCount) { if (acceptCount > 0) this.acceptCount = acceptCount; }
public int getAcceptCount() { return acceptCount; }
/**
* Acceptor thread count.
*/
protected int acceptorThreadCount = 1;
public void setAcceptorThreadCount(int acceptorThreadCount) {
this.acceptorThreadCount = acceptorThreadCount;
}
public int getAcceptorThreadCount() { return acceptorThreadCount; }
// 省略一些代碼
private boolean bindOnInit = true;
public boolean getBindOnInit() { return bindOnInit; }
public void setBindOnInit(boolean b) { this.bindOnInit = b; }
private volatile BindState bindState = BindState.UNBOUND;
private Executor executor = null;
public void setExecutor(Executor executor) {
this.executor = executor;
this.internalExecutor = (executor == null);
}
public Executor getExecutor() { return executor; }
public abstract void bind() throws Exception;
public abstract void startInternal() throws Exception;
public void init() throws Exception {
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
if (this.domain != null) {
// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
Registry.getRegistry(null, null).registerComponent(this, oname, null);
for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
registerJmx(sslHostConfig);
}
}
}
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
// 省略一些代碼
}
- acceptCount、acceptorThreadCount等很多都是端點的屬性,上述代碼沒有全部包括;
- executor表示處理請求用的工作線程池,internalExecutor變量表示該線程池是否由內(nèi)部創(chuàng)建,外部線程池是指server.xml中Connector元素executor屬性引用的線程池;
- Acceptor是靜態(tài)內(nèi)部類,表示Acceptor線程;
- 在init函數(shù)中,bindOnInit是一個布爾值,true表示在init時綁定地址,false表示在start時綁定地址,默認(rèn)是true。初始化時會調(diào)用bind抽象方法并做與JMX相關(guān)的工作;
- start函數(shù)可以看到bindOnInit的作用,若還未綁定則先綁定再調(diào)用startInternal抽象方法。
屬性賦值
端點的屬性是在何時被賦值的呢?這還要回到前文所述的解析server.xml的過程中。在解析server.xml時為Server/Service/Connector創(chuàng)建了一個ConnectorCreateRule和一個SetAllPropertiesRule。
ConnectorCreateRule創(chuàng)建了Connector實例,并調(diào)用ProtocolHandler如AbstractProtocol的setExecutor方法將executor屬性值引用的外部工作線程池設(shè)置到與AbstractProtocol關(guān)聯(lián)的AbstractEndpoint上,sslImplementationName同理:
@Override
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
Service svc = (Service)digester.peek();
Executor ex = null;
if ( attributes.getValue("executor")!=null ) {
ex = svc.getExecutor(attributes.getValue("executor"));
}
Connector con = new Connector(attributes.getValue("protocol"));
if (ex != null) {
setExecutor(con, ex);
}
String sslImplementationName = attributes.getValue("sslImplementationName");
if (sslImplementationName != null) {
setSSLImplementationName(con, sslImplementationName);
}
digester.push(con);
}
private static void setExecutor(Connector con, Executor ex) throws Exception {
Method m = IntrospectionUtils.findMethod(con.getProtocolHandler().getClass(),"setExecutor",new Class[] {java.util.concurrent.Executor.class});
if (m!=null) {
m.invoke(con.getProtocolHandler(), new Object[] {ex});
}else {
log.warn(sm.getString("connector.noSetExecutor", con));
}
}
SetAllPropertiesRule這個規(guī)則只排除了executor和sslImplementationName兩個屬性的賦值,并使用IntrospectionUtils.setProperty為屬性賦值。Connector元素上可配置的屬性列表可以參見官方文檔,可以分成三種類型:
- 只屬于Connector,如scheme等;
- 只屬于EndPoint,如bindOnInit、acceptCount等;
- 共存于Connector和EndPoint,如port、redirectPort等。
因此,屬性賦值也分為三種:
- 對于第一種屬性,IntrospectionUtils.setProperty會調(diào)用恰當(dāng)?shù)膕etter方法;
- 對于第二種屬性,IntrospectionUtils.setProperty會調(diào)用Connector的setProperty方法
該方法會接著在ProtocolHandler上賦值,AbstractProtocol的setProperty方法如下:public boolean setProperty(String name, String value) { String repl = name; if (replacements.get(name) != null) { repl = replacements.get(name); } return IntrospectionUtils.setProperty(protocolHandler, repl, value); }
接著調(diào)用AbstractEndPoint的setProperty方法,如果屬性名以socket.開頭那么將值設(shè)置socketProperties的對應(yīng)屬性上,否則設(shè)置到AbstractEndPoint的自身成員變量上:public boolean setProperty(String name, String value) { return endpoint.setProperty(name, value); }public boolean setProperty(String name, String value) { setAttribute(name, value); final String socketName = "socket."; try { if (name.startsWith(socketName)) { return IntrospectionUtils.setProperty(socketProperties, name.substring(socketName.length()), value); } else { return IntrospectionUtils.setProperty(this,name,value,false); } }catch ( Exception x ) { getLog().error("Unable to set attribute \""+name+"\" to \""+value+"\"",x); return false; } } - 對于第三種屬性,上述兩個賦值過程都會執(zhí)行。
NioEndpoint
NioEndpoint繼承了AbstractEndpoint抽象類,部分代碼如下:
public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
public static final int OP_REGISTER = 0x100; //register interest op
// ----------------------------------------------------------------- Fields
private NioSelectorPool selectorPool = new NioSelectorPool();
private ServerSocketChannel serverSock = null;
private volatile CountDownLatch stopLatch = null;
private SynchronizedStack<PollerEvent> eventCache;
private SynchronizedStack<NioChannel> nioChannels;
/**
* Priority of the poller threads.
*/
private int pollerThreadPriority = Thread.NORM_PRIORITY;
public void setPollerThreadPriority(int pollerThreadPriority) { this.pollerThreadPriority = pollerThreadPriority; }
public int getPollerThreadPriority() { return pollerThreadPriority; }
/**
* Poller thread count.
*/
private int pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors());
public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
public int getPollerThreadCount() { return pollerThreadCount; }
private long selectorTimeout = 1000;
public void setSelectorTimeout(long timeout){ this.selectorTimeout = timeout;}
public long getSelectorTimeout(){ return this.selectorTimeout; }
/**
* The socket poller.
*/
private Poller[] pollers = null;
private AtomicInteger pollerRotater = new AtomicInteger(0);
/**
* Return an available poller in true round robin fashion.
*
* @return The next poller in sequence
*/
public Poller getPoller0() {
int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;
return pollers[idx];
}
// 省略一些代碼
}
- NIO特定XML屬性的賦值過程同上;
- selectorPool是一個NioSelectorPool類型的選擇器池;
- serverSock是端點監(jiān)聽的監(jiān)聽套接字通道;
- SynchronizedStack是Tomcat自己實現(xiàn)的一個棧,入棧和出棧操作都是synchronized的。eventCache和nioChannels是兩個棧,分別存放輪詢事件和Nio通道。
1. 初始化
NioEndpoint實現(xiàn)了AbstractEndpoint類的bind抽象方法,這里看到了熟悉的ServerSocketChannel等Java NIO的內(nèi)容,打開通道和綁定地址:
@Override
public void bind() throws Exception {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
serverSock.socket().bind(addr,getAcceptCount());
serverSock.configureBlocking(true); //mimic APR behavior
// Initialize thread count defaults for acceptor, poller
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
}
setStopLatch(new CountDownLatch(pollerThreadCount));
// Initialize SSL if needed
initialiseSsl();
selectorPool.open();
}
2. 啟動
NioEndpoint實現(xiàn)了AbstractEndpoint類的startInternal抽象方法,代碼如下:
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@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());
// Create worker collection
if ( getExecutor() == null ) {
createExecutor();
}
initializeConnectionLatch();
// Start poller threads
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);
pollerThread.start();
}
startAcceptorThreads();
}
}
啟動過程做了以下幾件事:
- 先用getExecutor函數(shù)判斷端點是否已關(guān)聯(lián)外部的線程池(見上文屬性賦值的分析),若沒有則先調(diào)用createExecutor創(chuàng)建內(nèi)部工作線程池;
- initializeConnectionLatch函數(shù)利用maxConnections屬性創(chuàng)建了LimitLatch對象并賦值給connectionLimitLatch成員變量;
- 創(chuàng)建輪詢Poller線程(Poller是NioEndPoint的內(nèi)部類);
- 創(chuàng)建Acceptor線程(Acceptor是AbstractEndPoint和NioEndPoint的內(nèi)部類)。
createExecutor和startAcceptorThreads都定義在父類AbstractEndpoint中,代碼如下,其中的getName函數(shù)返回端點的名稱用以設(shè)置線程名稱(ProtocolHandler初始化時會給端點設(shè)置名稱,可以參閱上文Http11NioProtocol的初始化分析)。
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);
}
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());
t.start();
}
}
protected abstract Acceptor createAcceptor();
所以三種線程的名稱分別是:
- 工作線程的名稱需要看是外部線程池還是內(nèi)部線程池:若是外部線程池則是Executor元素的namePrefix屬性值加計數(shù),內(nèi)部線程池則是端點名稱加exec加計數(shù);
- 輪詢線程的名稱是端點名稱加ClientPoller加計數(shù);
- Acceptor線程的名稱是端點名稱加Acceptor加計數(shù)。
NioEndpoint實現(xiàn)的createAcceptor方法如下,Acceptor是NioEndpoint的成員內(nèi)部類:
@Override
protected AbstractEndpoint.Acceptor createAcceptor() {
return new Acceptor();
}
三種線程的作用請看下一篇文章。