美文网首页
Tomcat nio 网络初始化

Tomcat nio 网络初始化

作者: 绝尘驹 | 来源:发表于2019-07-07 17:16 被阅读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线程,主要是干两件事情:

    • 负责把新来的请求注册到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

    相关文章

      网友评论

          本文标题:Tomcat nio 网络初始化

          本文链接:https://www.haomeiwen.com/subject/eftgqctx.html