Tomcat nio 网络初始化
上一偏文章写tomcat的启动初始化,包含tomcat的init,load,start的三个核心部分,讲了tomcat的配置文件解析,工作线程创建,还有最最重要的一部分就是tomcat接受客户端请求的NIO网络模型
Tomcat nio网络模型的初始化配置,主要完成nio socket的参数配置
以及nio selector线程的创建等,初始化如下也是在tomcat的init模块完成的。
代码主要是StandardService的initInternal()方法是入口,
synchronized (connectorsLock) {
for (Connector connector : connectors) {
connector.init();
}
}
tomat server.xml里面每个<Connector > 对应一个connector,
connector.init 方法主要是
try {
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
protocolHandler默认是http/1.1对应实现类org.apache.coyote.http11.Http11NioProtocol,protocolHandler是通过
Http11NioProtocol的构造函数创建的,代码如下:
public Http11NioProtocol() {
super(new NioEndpoint());
}
Http11NioProtocol 有个endpoint,这个endpoint是创建nio的核心,我们等下就会分析,先看下tomcat的一下tcp层面的默认参数
Http11NioProtocol继承AbstractProtocol,他的构造函数如下:
public AbstractProtocol(AbstractEndpoint<S,?> endpoint) {
this.endpoint = endpoint
setConnectionLinger(Constants.DEFAULT_CONNECTION_LINGER);
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
这里我们看到两个tcp层面的参数,分别是Linger和tcpNoDelay,Linger默认是:
public static final int DEFAULT_CONNECTION_LINGER = -1;
public void setConnectionLinger(int connectionLinger) {
socketProperties.setSoLingerTime(connectionLinger);
socketProperties.setSoLingerOn(connectionLinger>=0);
}
LINGER设置为-1时,LingerOn是false,即0,这等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;
TcpNoDelay 的默认值是:
public static final boolean DEFAULT_TCP_NO_DELAY = true;
即关掉tcp禁止小包发送的算法,可以发送小包
看完了tomcat的再创建Http11NioProtocol时设置的网络层默认的参数后,我们下面就看怎么创建nio 的,即上面的protocolHandler.init()实现。
protocolHandler.init()的主要是执行上面说的endPoint的init方法,大戏就是从NioEndpoint的init方法开始的。
public final void init() throws Exception {
if (bindOnInit) {
bindWithCleanup();
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);
ObjectName socketPropertiesOname = new ObjectName(domain +
":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties");
socketProperties.setObjectName(socketPropertiesOname);
Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);
for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
registerJmx(sslHostConfig);
}
}
}
bindWithCleanup 主要是执行bind方法
public void bind() throws Exception {
//创建server socket,即bind操作,同时设定了backlog,并标记为阻塞模式,这个阻塞是对accept操作的。
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();
//创建selector,但这里的selector不是接受请求的selector
selectorPool.open(getName());
}
NioEndpoint的init方法就完了,tomcat在start阶段时,会执行connector.start()方法,start最终也是执行NioEndpoint的startInternal方法
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
//tomcat对频繁用的对象做了缓存重用,缓存大小默认时128
if (socketProperties.getProcessorCache() != 0) {
//processorCache 是tomcat的puller线程会为每个请求创建一个SocketProcessor,缓存里有就不用创建新的
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
//一个新的请求进来都会一个PollerEvent,通过这个event来注册到poller的selector线程上,eventCache用来缓存PollerEvent
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
if (socketProperties.getBufferPool() != 0) {
////一个新的请求进来,都会从nioChannels里看又没有可以复用的NioChannel,没有就新创建一个NioChannel。
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}
// Create worker collection
//这里是重新检查下,已经在init阶段初始化完成了。
if (getExecutor() == null) {
createExecutor();
}
//tomcat对最大链接数有个限制,默认是10000,超过了就不接受,由maxConnections参数指定,-1是没有限制
initializeConnectionLatch();
// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
//创建acceptor线程,负责接收链接
startAcceptorThread();
}
}
Puller 线程
Poller是对jdk nioselector的一个封装,核心是selector,
public Poller() throws IOException {
this.selector = Selector.open();
}
tomcat10对poller线程做了修改,之前是两个,现在是一个poller线程,主要是干两件事情:
- 负责把新来的请求注册到selector上。
- 处理链接上的读写事件,通过创建一个SocketProcessor,交给tomcat work线程去处理,poller线程本身并不做io操作
这也是tomcat只留了一个poller线程来处理读写事件,但是大量链接的场景,就不能利用多core
上面我们看到tomcat用了三个对象池技术,因为这三个是频繁使用的,而且都是puller线程用的,进一步优化来puller线程的性能。
Acceptor 线程
Acceptor 线程是阻塞的,代码如下:
try {
//if we have reached max connections, wait
//检查是否达到了链接数限制,达到了则要等其他链接释放
endpoint.countUpOrAwaitConnection();
//去accept已经建立链接的请求
}
countUpOrAwaitConnection 代码如下:
protected void countUpOrAwaitConnection() throws InterruptedException {
if (maxConnections==-1) return;
LimitLatch latch = connectionLimitLatch;
if (latch!=null) latch.countUpOrAwait();
}
如果是maxConnections是-1,则不会等其他链接释放,如果不是,则看链接数是否达到了限制,如果达到则要阻塞在这里。
tomcat的初始化nio 线程就算是写完了,想要知道nio是怎么把请求交给catalina work线程的,请看我的 [NIO 线程模型分析的文章]https://www.jianshu.com/p/4e239e217ada