tomcat8.5


Tomcat 

主要功能是提供HTTP的服务器。底层使用TCP/IP的进行的socket通信。它遵循了Servlet的规范、进行了具体实现,完成servlet容器的管理。

连接:服务器与客户端进行的Socket通信的连接。

请求:客户端向服务器发送数据。

应答:服务器向客户端发送数据。

HTTP协议:服务器与客户端之间交流的一种协议,通过规定好的结构来识别相互的内容。

BIO(blocking io) 阻塞IO,NIO(non-blocking io) 非阻塞IO,AIO(Asynchronous io) 异步IO


一 HTTP请求协议简介

请求包:

METHOD URL?KEY=VAL VERSION
HEAD_KEY1: HEAD_VAL1
HEAD_KEY2: HEAD_VAL2

BODY
GET /user/userInfos?userIds=123,456,789 HTTP/1.1
Token:ASBJ4KF3SEWDSKNL53H
Host:www.ezone.com
Cookie:XXX=xxx;YYY=yyy

METHOD: 请求的方式例如 GET POST ...
HEAD_KEY: 请求头的名称。
HEAD_VAL: 请求头名称对应的值。
每一行使用\r\n分割。
BODY 请求携带的内容。与请求头之间空出一个空行

应答包:

VERSION STATUS MSG
HEAD_KEY1:HEAD_VAL1
HEAD_KEY2:HEAD_VAL2

BODY
HTTP/1.1 200 OK
Content-Type:application/json;charset=UTF-8
Content-Length:23

{"name":"zzz", state:0}

二 JAVA 使用 TCP实现

JAVA 实现中TCP的通信实现。分别为 BIO NIO NIO2(异步非阻塞 AIO的一种)

扫描二维码关注公众号,回复: 11230629 查看本文章

1 BIO

服务器

// 服务器
ServerSocket server = new ServerSocket(port, backlog);
// 阻塞并接受客户端的连接、成功返回Socket对象。
Socket client = server.accept();
// 向客户端发送数据的流
OutputStream out = client.getOutputStream();
// 接受客户端当数据的流
InputStream in = client.getInputStream();

客户端

// 客户端创建Socket 与服务器进行连接
Socket client = new Socket(host, port);
// 向服务器发送数据的流
OutputStream out = client.getOutputStream();
// 接受服务器数据的流
InputStream in = client.getInputStream();

2 NIO

服务器

// 开启多路复用选择器
Selector selector = Selector.open();
// 创建server socket 通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8888), 1000);
// NIO模式 为非阻塞、设置为非阻塞,否则无法注册。
serverSocketChannel.configureBlocking(false);
// 注册到Selector上、设置选择事件OP_ACCEPT、当有连接请求接入的时候会触发。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
    // 3s 轮训的延迟
    selector.select(3000);
    // 有触发SelectionKey选择的事件将会通过这个方式得到。
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    Iterator<SelectionKey> iterator = selectionKeys.iterator();
    while (iterator.hasNext()) {
        SelectionKey selectionKey = iterator.next();
        // 此处进行消费掉!!
        iterator.remove(); 
        // 有客户端连接的请求接入
        if (selectionKey.isAcceptable()) {
            ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();
            SocketChannel client = server.accept();
            // 设置为非阻塞才能注册到 Selector
            client.configureBlocking(false);
            // 将接入的客户端也注册到多路复用上、有可读数据时会得到这SelectionKey
            client.register(selector, SelectionKey.OP_READ);
        }

        if (selectionKey.isReadable()) {
            
            SocketChannel client = (SocketChannel)selectionKey.channel();
            try {
                // client.read(..)
                // client.write(..)
                // client.read(..)
            } catch(Exception e) {
                selectionKey.cancel();
                client.close();
            }
        }
    }
}

客户端:(客户端与服务端注册使用方式一致, 此处主写服务器 客户实现省略...)

3 AIO

// 异步IO使用的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(executorService);
// 创建 Socket 服务器
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group);
serverChannel.bind(new InetSocketAddress(8888), 1000);

Object attachment = null; // 全局的一个对象(可以自定义为任意)
// 接受请求,通过异步回调方式得到客户端
server.accept(attachment, new CompletionHandler<AsynchronousSocketChannel, Object>(){
    @Override
    public void completed(AsynchronousSocketChannel client, Object attachment) {
        // 异步IO每次触发事件都需要重新注册。一次注册一次执行。
        serverChannel.accept(attachment, this);

        ByteBuffer buf = ByteBuffer.allocate(1024);
        Object clientAttachment = null;
        client.read(buf, clientAttachment, new ReaderHandler(client, buf, attachment));
    }

    public void failed(Throwable exc, Object attachment) {...}
});

public class ReaderHandler implements CompletionHandler<Integer, Object> {
    AsynchronousSocketChannel client;
    ByteBuffer buf;
    Object attachment;
    // 构造省略
    public ReaderHandler(....) {....}

    public void completed(Integer result, Object clientAttachment) {
        buf.flip(); // 转化为读数据
        byte[] data = new byte[byteBuffer.remaining()];
        byteBuffer.get(data);
        System.out.println(new String(data, Charset.forName("UTF-8")));
        // 清理buf 可以继续写数据
        byteBuffer.clear();

        // 读取完毕需要重新继续注册
        client.read(buf, clientAttachment, this);
    }
}

三 Tomcat 的设计

tomcat 分成两部分:

1 HTTP 服务器的功能,通过Socket(Tcp/Ip) 进行通信。进行http的协议解析与包装
2 Servlet 容器实现

tomcat的生命周期

通过 org.apache.catalina.Lifecycle 接口进行管理。
它维护了tomcat init() start() stop() destroy() 的过程。通过子类一层层调用来完成功能。

tomcat的结构

它的结构可以通过 server.xml 来确认

Xml 的定义与tomcat的组件实现一一对应,其组件均实现了.Lifecycle 接口。

Server 代表一个tomcat 其实现为:org.apache.catalina.core.StandardServer

Server下允许存在多个 Service : org.apache.catalina.core.StandardService

Service 是对外提供的一种业务,通常使用的时候只配置过一个,但tomcat实现了允许配置多个。

Service 下包含了两主要部分:

    1 Connector 用于绑定端口、并处理请求与应答的协议,可以配置多个。
    2 Engine 是 Servlet核心部分、容器的具体实现、每个业务只允许配置一个。org.apache.catalina.core.StandardEngine

tomcat的服务是多个组件之间的组合、相互关联来完成具体功能。

当Server 调用init()时候,会依次调用其子组件的init()方法
server.init()->service.init() -> (connector/engine).init()
server.start()->service.start() -> (connector/engine).start()

Connector 连接器组件,完成HTTP服务器功能。
ProtocolHandler:
    Endpoint进行绑定TCP的连接
    Adapter调用Servlet的实现,将请求适配为HttpServletRequest进行传递

Engine组件为最核心的一部分实现
    Host:代表一个虚拟主机、name指定当域名为设定值时候使用当前主机配置。
        Context:代表当前域名主机下的一个Web应用(包含很多应用业务的Servlet实现)
            Wrapper: 包装一个Servlet的实现

   如果Host域名不存在则会使用Engine指定的defaultHost="localhost"的虚拟主机

当请求进入当时候,通过Connector 进行接受连接。
ProtocolHandler 初始化了连接当绑定模式。
Adapter 接受请求将服务请求适配成HttpServerRequest 并调用Servlet进行业务执行。

四 tomcat 中的ClassLoader

在Tomcat中,每个应用程序的部署它们虽然允许在一个tomcat服务内,但均有各自的classpath,每个应用程序之间依赖Jar位置相互独立。实际上是因为Tomcat自己实现了应用的classloader。

上图是tomcat启动时所创建的用于启动tomcat的classLoader 初始化。

1 简介tomcat的类加载器

CommonLoader 用于加载通用的类加载,目标位置 tomcat/lib/*.jar
CatalinaLoader 服务器加载的加载器,仅服务器可见,应用程序不可见
SharedLoader 加载应⽤程序时共享的类加载器。

WebappClassLoader 用来加载web的应用程序,默认从/webproject/WEB-INF/classes 和 /webproject/WEB-INF/lib 加载

Tomcat 有自己的一套类加载顺序。

CommonLoader 作为加载器的父加载器,CommonLoader加载的可以被SharedLoader,CatalinaLoader使用。
WebAppClassLoader 可以从SharedLoader获得加载的类、如果类不存在则从自身位置加载。每个同层的加载器之间又能相互隔离。每个 WebAppClassLoader都是同级关系

Tomcat 没有完全遵循双亲委派模型

双亲委派模型:每个加载器加载先让父加载器加载,如果父加载器均不存在则从自己加载。
WebappClassLoader加载器则是先自己进行加载,加载不了再交给CommonLoader进行双亲委派的方式。

2 自定义类加载器方式

继承ClassLoader重写

// 通过继承 ClassLoader 的方式加载
public class MyClassLoader1 extends ClassLoader{
	
    File classPath;
    public MyClassLoader1(File classPath) {
        this.classPath = classPath;
        // 这里需要处理路径下的*.jar
        // 省略代码 classpath 下的 *.Jar
    }
    // 重写 loadClass
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        String path = name.replace(".", "/") + ".class";
        InputStream in = getResourceAsStream(name);
		
        byte[] data = ...;
        //.... read 逻辑省略
        // 加载当前的 Class
        Class<?> aClass = super.defineClass(name, data, 0, data.length);
        if  (aClass == null) {
            aClass = super.loadClass(name, resolve);
        }
        if (aClass == null) {
            throw new ClassNotFoundException(name);
        }
        return aClass;
    }
    // 重写 getResource
    @Override
    public URL getResource(String name) {
        
        return ...;
    }
}

继承URLClassLoader重写

// 通过继承URLClassLoader的方式,相对简单
public class MyClassLoader2 extends URLClassLoader {
    // URL是*.jar 或 classpath的位置
    public MyClassLoader2(URL[] urls) {
        super(urls);
    }

    @Override
    public void addURL(URL url) {
		
        super.addURL(url);
    }
}

3 tomcat的配置说明

Connector 
port:端口,例如8080,默认为0,表示随机一个端口。
address:监听的地址,默认为所有地址。
acceptCount:等待队列的最大长度,所有处理线程被占用,新的请求将放入队列等待。
maxConnections:最大连接数。
maxThreads:最大线程数,如果配置executor共享线程池,该属性被忽略。

protocol:使用连接协议
  org.apache.coyote.http11.Http11NioProtocol
  org.apache.coyote.http11.Http11Nio2Protocol
redirectPort:接收客户端请求为HTTPS时,转发到这个端口
connectionTimeout:客户端连接,指定的时间没有进行请求则发生连接超时。
enableLookups:Host进行DNS查询获得客户端主机,默认为true,设置为false则不DNS解析;


Engine

<engine name="Catalina" defaultHost="localhost">
  <Host name="localhost" appBase="webapps"></Host>
</engine>

defaultHost 当请求当Host不存在时候,使用默认值,例如请求www.xx.com,不存在这个域名当虚拟主机,则使用localhost当主机接受。


Host

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"/>

name 是这个虚拟主机当域名映射。
appBase 是物理路径
unpackWARs 启动解压WAR
autoDeploy 启动状态更改程序文件是否自动部署


Context
<Host ...><Context path="/" docBase="ROOT"/></Host>

path 是contextPath,URL请求当前缀。
docBase Web项目的物理路径。请求会进入这个web程序执行。

四 tomcat 源码

Tomcat的设计,套娃式架构设计

1. 套娃式架构设计的好处: 

    便于组件生命周期的管理,每个组件在服务器启动开始都将开始自己的生命周期,他们共同实现一个生命周期定义的接口Lifecycle,几乎模版化的执行过程,调用关系变得清晰。
    tomcat启动时从server.xml读取配置便于组装tomcat/server实例,因为xml配置层次与框架层次对应,并容易理解。
    子容器更好继承父容器一些配置。

2. 解读源码好处

    可以学习优秀的设计,增强开发的经验,和了解优秀的逻辑实现。
    提高架构思维,便于深入了解一个框架或项目,对性能调优也有帮助。

3. tomcat 源码导入(maven方式)

    下载tomcat8.5源码包,解压。自行创建pom.xml,通过编辑器 maven 导入。这样可以简单的导入并启动tomcat。

4 tomcat启动入口

org.apache.catalina.startup.Bootstrap main(args[])

// 简单介绍局部代码
class org.apache.catalina.startup.Bootstrap {

public static void main(String args[]) {

        try{ 
            // 创建 classloader
            // commonClassLoader
            // catalinaClassLoader
            // sharedClassLoader
            // 创建 Catalina对象
            bootstrap.init();
                
            // ....省略无关代码
             
            // 调用 Catalina load方法, 进行服务器的init调用
            // server.init() -> service.init() -> (connector|engine).init()
            daemon.load(args);

            // 调用 Catalina start方法,进去服务器的start调用
            // server.start() -> service.start() -> (connector|engine).start()
            daemon.start();
            
            // ... 省略无关代码
        } catch (Throwable t) {
          // .... 省略无关代码
        }
    }
}

5 tomcat 容器组件

tomcat 组件的实现均实现 Lifecycle,进行维护生命周期,拥有统一生命周期执行过程。

public interface Lifecycle {
    public void init() throws LifecycleException;
    public void start() throws LifecycleException;
    public void stop() throws LifecycleException;
    public void destroy() throws LifecycleException;
}

其组件包含多个子组件组成的一个服务容器。用来实现HTTP请求到Servlet的业务处理。

· 初始化阶段: (可以断点) load() 执行进行跟进。

    load()方法实际上是通过反射执行 catalina.load()方法。在 catalina.load方法中进行解析了server.xml文件得到Server的实例。开始进行初始化。

Server 代表这个服务器,包含子组件:Service[]

在Server的Init的实现中可以看到它将所有的Service 进行了初始化

Service 组件中包含了:Connector[] 和 Engine 等组件。
Connector[] 和 Engine 是其中重要的组成。

Connector 负责建立TCP连接、接受客户端请求与请求的协议处理。
Engine 负责Web应用的容器实现、主要是Servlet容器实现。

Connector 接受到的请求会调用 Engine 中Servlet 进行处理,处理应答通过Connector建立的连接返回。

在Service 中分别进行了Engine.init() 和 Connector[i].init();

源码中可以看出 一个Service 允许监听多个端口,和通过多种协议进行处理。


猜你喜欢

转载自blog.csdn.net/zxy2711352/article/details/105974979