Tomcat nio 网络初始化

2019-07-07  本文已影响0人  绝尘驹

上一偏文章写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线程,主要是干两件事情:

这也是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

上一篇下一篇

猜你喜欢

热点阅读