概述

    从入门Web开始一直在使用Tomcat,随着对网络相关的知识的进一步了解,觉得越有必有去阅读一下常用的开源服务器的整个工作流程,以及使用场景,对比几款服务器的优劣势、最终根据合适的业务场景进行优化。于是有了这一篇启动相关的源码分析,使用到的 Tomcat版本为  9.0.6 ,技术有限,难免出现错误,欢迎指出,也期待各路大神指点。

首先启动Tomcat方式这里采取编程的方式,maven引入坐标

        <dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.6</version>
</dependency>

1.启动方法

        Tomcat tomcat = new Tomcat();
Connector connector = new Connector();
connector.setPort(8080);
tomcat.setConnector(connector);
tomcat.start();
tomcat.getServer().await();
  1. Connector 的构造方法默认会使用Http11NioProtocol协议(NIO)来对客户端连接进行处理,通过反射获得其对象,赋值给 protocolHandler
  2. tomcat.setConnector()会初始化一个 StandardServer和StandardService,并且将StandardServicet添加到StandardServer里,Connector添加 StandardService里,因此这里三者之间的关系就是
  3. Tomcat.start() 则利用初始化的StandardServer,调用其start()方法
  4. Http11Nioprotocol 协议是用来处理Nio的协议,在它的构造方法里,实例化了NioEnpoint 交给 AbstractProtocol 保管,然而真正实现Nio建立网络连接则是通过该类完成。后面细说是如何到NioEnponit的

  Connector默认使用Nio协议

public Connector() {
this("org.apache.coyote.http11.Http11NioProtocol");
} public Connector(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector(); if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
} else {
protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
}
} else if ("AJP/1.3".equals(protocol)) {
if (aprConnector) {
protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
} else {
protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
}
} else {
protocolHandlerClassName = 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;
}

2.LifecycleBase.start()  初始化与启动

  

@Override
public final synchronized void start() throws LifecycleException {
//state默认是 LifecycleState.NEW,首次执行时,先通过init()方法实现初始化
if (state.equals(LifecycleState.NEW)) {
init();
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
//初始化之后,开始启动tomcat服务器
startInternal();
     ...........
}

  

  

  1. LifecycleBase 是 StandardServer、Connector、StandardService的父类,当StandardServer的 start() 方法被调用时,将会调用父类LifecycleBase的 start() 方法,同样的,对Connector、StandardService来说,调用 start() 方法也会调用父类的LifecycleBase的start()方法。

  2. LifecycleBase 中持有 用 volatile 关键字 修饰的state 字段,该字段用来标识 Tomcat 启动时所处的状态,默认是 LifecycleState.NEW,因此进入init() 方法 
  3. 每个LifecycleState枚举类一 一对应着Tomcat 服务器的状态,每个枚举类状态又有对应的Tomcat启动的事件,可以通过实现  LifecycleListener 接口进行自定义。在Tomcat启动时会被放进 lifecycleListeners 中
public enum LifecycleState {
NEW(false, null),
INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
STARTING(true, Lifecycle.START_EVENT),
STARTED(true, Lifecycle.AFTER_START_EVENT),
STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
STOPPING(false, Lifecycle.STOP_EVENT),
STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
FAILED(false, null); private final boolean available;
private final String lifecycleEvent;

3.LifecycleBase.init()  初始化StandardServer

public final synchronized void init() throws LifecycleException {
if (!this.state.equals(LifecycleState.NEW)) {
this.invalidTransition("before_init");
} try {
//从lifecycleListeners 获取 before_init事件进行调用
this.setStateInternal(LifecycleState.INITIALIZING, (Object)null, false);
this.initInternal();
//对应 after_init 事件
this.setStateInternal(LifecycleState.INITIALIZED, (Object)null, false);
} catch (Throwable var2) {
this.handleSubClassException(var2, "lifecycleBase.initFail", this.toString());
}
}
  1. init() 方法是通过调用子类实现的initInternal() 方法来进行抽象的扩展,相当于一个没有具体实现的抽象函数,交给子类去初始化处理(这里是StandardServer 的调用过程)
  2. 在初始化方法调用的前后,使用了setStateInternal() 方法进行捕捉,这里便是对 实现了 LifecycleListener 接口的监听器做了实现,在其他地方,如:start、stop 都有进行 listener 的调用

4.StandardServer.initInternal() 初始化 server

   @Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// ...... 暂时不关心中间代码
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
//这里调用StandardService的init()方法
services[i].init();
}
}
  1. 步骤3 处理逻辑大致相同,init()方法里 是通过调用子类实现的 initInternal() 方法来初始化,同样的捕捉初始化的前后事件  before_init、after_init

5. StandardService.initInternal()  初始化service

@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
//省略若干......
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
//此处调用connector进行初始化
connector.init();
}
}
}
  1. 此处同理,调用 connector.init(),通过父类调用子类实现的 钩子函数 initInternal() 来初始化Connector

6.Connector.initInternal() 初始化connector

  

@Override
protected void initInternal() throws LifecycleException { super.initInternal();
//省略若干..............
try {
//初始化 Http11NioProtocol协议
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
  1. protocolHandler是我们在实例化 Connector 时,使用 默认的Http11NioProtocol协议。
  2. 其init()方法,则在父类 AbstractProtocol 中实现

6.AbstractProtocol.init()  初始化协议

  

    @Override
public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
} //省略若干行... String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
//初始化NioEndpoint
endpoint.init();
}
  1. NioEndpoint 的init() 方法,在其父类AbstractEndpoint中实现,用来把公共的抽象到父类中实现(如注册JMX),
  2. 然后再提供给子类bind() 方法,通过实现该方法来完成一次函数的回调
public final 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);
}
}
}

7. NioEndpoint.bind()

@Override
public void bind() throws Exception {
//初始化ServerSocker
initServerSocket(); // Initialize thread count defaults for acceptor, poller
//如果acceptor线程个数小于0,则默认为1
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
//如果poller线程数小于0,则默认为1
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
} //获取一个栅栏,通过AQS实现的一个共享锁
setStopLatch(new CountDownLatch(pollerThreadCount)); // Initialize SSL if needed
//初始化ssl配置
initialiseSsl(); selectorPool.open();
}
  1. 第一步初始化Socket线程

  1. 调用initServerSocket()方法,初始化Socket线程,可见这里线程为阻塞,目的能解释得通的也就是为了操作方便,但后续建立连接后的socket,会设为非阻塞的
  2. 初始化accptor与poller线程个数(分别为1 , 2) 
  3. 获取栅栏 CountDownLatch,用于在在高并发的场景时,允许并发请求个数最大值,默认 10000
  4. 这里需要提出一个Acceptor 和 Poller 的概念。Acceptor主要的职责就是监听是否有客户端套接字连接,并接收套接字,连接之后,把Socket 注册到Poller轮询器中的Selector中,如图

总结:

  到此为止初始化的过程已经结束,大致过程也就是通过把公共的东西交给高度抽象的父类处理,然后父类去调用提供给子类实现的回调函数,此时利用组合模式,在每次初始化对象完毕之后,再将其持有的对象调用其共同的父类的初始化方法,这样便十分简洁。后续将继续回到StandardServer的父类调用init()方法完毕时继续往下走,执行 startInternal()方法,但大致也与init 时相同,便将核心的加载方法拿出来。

8. NioEndpoint. startInternal()  启动Nio相关线程池

  

@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()); //创建工作的线程池,默认最小10,最大200
// Create worker collection
if ( getExecutor() == null ) {
createExecutor();
} initializeConnectionLatch(); //创建 2个 Poller 线程
// 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();
}
//启动1个Acceptor线程
startAcceptorThreads();
}
}
  1. 到此为止,Tomcat便已经启动完成。这里一共启动了 1个Acceptor线程,2个poller线程,10个work线程(启动时,不考虑有连接数并发超过10个任务)

Tomcat之NIO 启动与应用分析的更多相关文章

  1. Java网络编程与NIO详解11:Tomcat中的Connector源码分析(NIO)

    本文转载 https://www.javadoop.com 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.c ...

  2. 详解Tomcat系列(一)-从源码分析Tomcat的启动

    在整个Tomcat系列文章讲解之前, 我想说的是虽然整个Tomcat体系比较复杂, 但是Tomcat中的代码并不难读, 只要认真花点功夫, 一定能啃下来. 由于篇幅的原因, 很难把Tomcat所有的知 ...

  3. tomcat bio nio apr 模式性能测试

    转自:tomcat bio nio apr 模式性能测试与个人看法 11.11活动当天,服务器负载过大,导致部分页面出现了不可访问的状态.那后来主管就要求调优了,下面是tomcat bio.nio.a ...

  4. Tomcat处理HTTP请求源码分析(上)

    Tomcat处理HTTP请求源码分析(上) 作者 张华 发布于 2011年12月8日 | 8 讨论 分享到: 微博 微信 Facebook Twitter 有道云笔记 邮件分享 稍后阅读 我的阅读清单 ...

  5. ubuntu 14 中tomcat的开机启动设置

    开机自启动,将要执行的语句写入/etc/rc.local. #!/bin/sh -e # # rc.local # # This script is executed at the end of ea ...

  6. Tomcat处理HTTP请求源码分析(下)

    转载:http://www.infoq.com/cn/articles/zh-tomcat-http-request-2 很多开源应用服务器都是集成tomcat作为web container的,而且对 ...

  7. tomcat登陆WEB显示无权限问题&& tomcat无限循环启动问题

    tomcat登陆WEB显示无权限问题 The user specified as a definer (”@’%') does not exist 原因分析 因为创建视图使用的是xff@%用户(目前已 ...

  8. SpringBoot应用部署到Tomcat中无法启动问题

    SpringBoot应用部署到Tomcat中无法启动问题   背景 最近公司在做一些内部的小型Web应用时, 为了提高开发效率决定使用SpringBoot, 这货自带Servlet容器, 你在开发We ...

  9. 三种Tomcat集群方式的优缺点分析

    三种Tomcat集群方式的优缺点分析 2009-09-01 10:00 kit_lo kit_lo的博客 字号:T | T 本文对三种Tomcat集群方式的优缺点进行了分析.三种集群方式分别是:使用D ...

随机推荐

  1. SQL 优化案例

    create or replace procedure SP_GET_NEWEST_CAPTCHA( v_ACCOUNT_ID in VARCHAR2, --接收短信的手机号 v_Tail_num i ...

  2. 算法(Algorithms)第4版 练习 2.2.11(2)

    关键代码: private static void sort(Comparable[] input, int lo, int hi) { if(lo >= hi)//just one entry ...

  3. ubantu删除文件(夹)

    格式:rm -rf 目录名字 -r 就是向下递归,不管有多少级目录,一并删除 -f 就是直接强行删除,不作任何提示的意思 名称 rm - 移除文件或者目录 概述 rm [选项]... 文件列表... ...

  4. WebForm 使用点滴。。。。

    WebForm使用方式与WinForm很是相似,可控性非常高! 1.调用别的按钮事件: BtnSelect_Click(sender, e);

  5. Linux配置redis服务器

    1.安装redis 2.开启6379端口,使外部机器能够访问 3.

  6. python-socket2

    UDP,服务端 #! /usr/bin/env python #coding=utf-8 import socket #创建socket,指定ipv4,udp类型 s = socket.socket( ...

  7. Python习题-输出一个字符串中最长的子字符串及其长度

    描述:有个字符串$sd1#111$svda123!!!221&eSSDSDG,包含特殊字符.数字和字母,输出最长的子字符串和他的长度#例如上面的字符串包含数字字母的字符串是svda123,长度 ...

  8. linux命令学习笔记(8):cp 命令

    cp命令用来复制文件或者目录,是Linux系统中最常用的命令之一.一般情况下,shell会设置一个别名, 在命令行下复制文件时,如果目标文件已经存在,就会询问是否覆盖,不管你是否使用-i参数.但是如果 ...

  9. cocos2d-x CSV文件读取 (Excel生成csv文件)

    实现类 CCSVParse.h #ifndef __C_CSV_PARSE__ #define __C_CSV_PARSE__ #include "cocos2d.h" #incl ...

  10. 【leetcode刷题笔记】Minimum Depth of Binary Tree

    Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shor ...