Tomcat進階學(xué)習下篇

4霎烙、Tomcat源碼分析

4.1源碼構(gòu)建

下載

下載地址 https://tomcat.apache.org/download-80.cgi 下載src源碼然后解壓

配置文件

將配置文件轉(zhuǎn)移新的文件夾下避免沖突:

在 apache-tomcat-8.5.50-src ?錄中創(chuàng)建 source ?件夾

將 conf狈谊、webapps ?錄移動到剛剛創(chuàng)建的 source ?件夾中

配置JSP初始化器

ContextConfig類中的configureStart?法中增加??代碼將 Jsp 引擎初始化栈暇。

webConfig();

//初始化jsp引擎

context.addServletContainerInitializer(new JasperInitializer(), null);

啟動參數(shù)

-Dcatalina.home=D:/Study/tomcat-source/source

-Dcatalina.base=D:/Study/tomcat-source/source

-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager

-Djava.util.logging.config.file=D:/Study/tomcat-source/source/conf/logging.properties

D:/Study/tomcat-source/source這個就是剛剛咱們創(chuàng)建的文件夾 到時候更換成自己的文件夾

添加PomXml闹司,將tomcat交給maven管理

POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>apache-tomcat-8.5.50-src</artifactId>
    <name>Tomcat8.5</name>
    <version>8.5</version>
    <build>
        <!--指定源?錄-->
        <finalName>Tomcat8.5</finalName>
        <sourceDirectory>java</sourceDirectory>
        <resources>
            <resource>
                <directory>java</directory>
            </resource>
        </resources>
        <plugins>
            <!--引?編譯插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <!--tomcat 依賴的基礎(chǔ)包-->
    <dependencies>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>ant</groupId>
            <artifactId>ant</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
            <version>1.6.2</version>
        </dependency>
        <dependency>
            <groupId>javax.xml</groupId>
            <artifactId>jaxrpc</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jdt.core.compiler</groupId>
            <artifactId>ecj</artifactId>
            <version>4.5.1</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.soap</groupId>
            <artifactId>javax.xml.soap-api</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>
</project>

注意這個Pom需要自己創(chuàng)建嗓蘑。

運行

直接運行Bootstrap中的main函數(shù)即可剥懒。

Tomcat進階學(xué)習下篇

4.2Tomcat啟動流程源碼分析

4.2.1Tomcat啟動時序圖

通過時序圖觀察内舟,通過Bootstrap中的main方法啟動,先進行初始化初橘,在通過load方法逐級的往下加載验游,加載完成之后通過start逐級的往下啟動。

所有的組件接口都繼承了Lifecycle頂級接口保檐,Lifecycle頂級接口指定了啟動耕蝉、銷毀、停止等規(guī)范夜只,相當于Spring中的BeanFactory垒在;Tomcat通過Lifecycle統(tǒng)一規(guī)定了各個組件的生命周期。

Tomcat進階學(xué)習下篇

4.2.2啟動源碼追蹤

1扔亥、加載流程

對應(yīng)時序圖中1~13步

Bootstrap

public static void main(String args[]) {
    //添加了一把鎖 
    synchronized (daemonLock) {
        if (daemon == null) {
            // 創(chuàng)建一個BootStreap實例
            Bootstrap bootstrap = new Bootstrap();
            try {
               //調(diào)用bootrap的實例化方法
                bootstrap.init();
            } catch (Throwable t) {
 ...
                return;
            }
 daemon = bootstrap;
        } else {
            // When running as a service the call to stop will be on a new
            // thread so make sure the correct class loader is used to
            // prevent a range of class not found exceptions.
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }
    }

    try {
        String command = "start";
       ... 
       if (command.equals("start")) {
 daemon.setAwait(true);
            //加載流程
 daemon.load(args);
           //啟動流程
 daemon.start();
            if (null == daemon.getServer()) {
                System.exit(1);
            }
        }
     ...
    } catch (Throwable t) {
      ...
    }
}

BootStrap#init方法

public void init() throws Exception {
    //初始化classloader  沒有使用系統(tǒng)的使用自定義的classLoader后邊在講解
    initClassLoaders();
    //將自定義的classloader綁定到線程上在使用的時候可以直接拿來使用 
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    //將自定義的classLoader設(shè)置到安全的classLoader上
    SecurityClassLoad.securityClassLoad(catalinaLoader);
    //通過自定義的classLoader加載Catalina Class 
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    //進行實例化
    Object startupInstance = startupClass.getConstructor().newInstance();

   //獲取到父類的類加載器方法-- 將自定義的類加載器設(shè)置到父類中
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues);
     //將Catalina實例賦值到 catalinaDaemon屬性上
    catalinaDaemon = startupInstance;
}

BootStrap#load方法

private void load(String[] arguments) throws Exception {
    // Call the load() method
    String methodName = "load";
    Object param[];
    Class<?> paramTypes[];
    if (arguments==null || arguments.length==0) {
        paramTypes = null;
        param = null;
    } else {
        paramTypes = new Class[1];
        paramTypes[0] = arguments.getClass();
        param = new Object[1];
        param[0] = arguments;
    }
    //【通過反射的方法調(diào)用Catalina的load方法】
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);
      method.invoke(catalinaDaemon, param);
}

Catalina#load方法

public void load() {
   //判斷是否加載過 默認為false
    if (loaded) {
        return;
    }
    loaded = true;

    long t1 = System.nanoTime();
    //初始化文件夾
    initDirs();

    // 初始名字Before digester - it may be needed
    initNaming();

    //保存Tomcat所需要的組件和監(jiān)聽器的ClassName场躯,比如StandardServer谈为、LifecycleListener... 
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try {
        try {
            //讀取conf/server.xml配置文件
            file = configFile();
            inputStream = new FileInputStream(file);
            inputSource = new InputSource(file.toURI().toURL().toString());
        } catch (Exception e) {}
       //創(chuàng)建輸入流
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream(getConfigFile());
                inputSource = new InputSource
                    (getClass().getClassLoader()
                     .getResource(getConfigFile()).toString());
            } catch (Exception e) {}
        }

        // 如果inputstream還不存在繼續(xù)創(chuàng)建
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                        .getResourceAsStream("server-embed.xml");
                inputSource = new InputSource
                (getClass().getClassLoader()
                        .getResource("server-embed.xml").toString());
            } catch (Exception e) {}
        }

        //如果還存在就返回吧 【省略了一些日志打印】
        if (inputStream == null || inputSource == null) { return; }

        try {
            inputSource.setByteStream(inputStream);
    //將當前對象推入到digester
            digester.push(this);
            //解析server.xml配置文件
            digester.parse(inputSource);
        } catch (Exception e) {return; }
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {}
        }
    }

   //給server設(shè)置一些屬性
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection 給System設(shè)置Out和ErrSystemLogHandler、SystemLogHandler 
    initStreams();

    // Start the new server
    try {

        getServer().init();
    } catch (LifecycleException e) {
        if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new java.lang.Error(e);
        } else {
 log.error("Catalina.start", e);
        }
    }
}

LifecycleBase的init方法和initInternal方法

public final synchronized void init() throws LifecycleException {
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        initInternal();
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.initFail", toString());
    }
}

protected abstract void initInternal() throws LifecycleException;

使用典型的模板設(shè)計模式踢关!

調(diào)用StandardServer中的initInternal方法

protected void initInternal() throws LifecycleException {
   //調(diào)用父類LifecycleBase的initInternal初始化屬性 
    super.initInternal();

    // Register global String cache
    // 初始化緩存組件    
    onameStringCache = register(new StringCache(), "type=StringCache");

    // 注冊 MBeanFactory
    MBeanFactory factory = new MBeanFactory();
    factory.setContainer(this);
    onameMBeanFactory = register(factory, "type=MBeanFactory");

    //初始化全局naming 
    globalNamingResources.init();

    // Populate the extension validator with JARs from common and shared
    //填充一些拓展jar包校驗 
    // class loaders
    if (getCatalina() != null) {
        ClassLoader cl = getCatalina().getParentClassLoader();
        // Walk the class loader hierarchy. Stop at the system class loader.
        // This will add the shared (if present) and common class loaders
        while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
            if (cl instanceof URLClassLoader) {
                URL[] urls = ((URLClassLoader) cl).getURLs();
                for (URL url : urls) {
                    if (url.getProtocol().equals("file")) {
                        try {
                            File f = new File (url.toURI());
                            if (f.isFile() &&
                                    f.getName().endsWith(".jar")) {
                                ExtensionValidator.addSystemResource(f);
                            }
                        } catch (URISyntaxException e) {
                            // Ignore
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
                }
            }
            cl = cl.getParent();
        }
    }
    // 初始化service
    for (int i = 0; i < services.length; i++) {
        services[i].init();
    }
}

services[i].init();實際上還是走的LifecycleBase的init方法通過模板調(diào)用StandardService中的initIternal方法伞鲫。

StandardService#initInternal方法

protected void initInternal() throws LifecycleException {

    super.initInternal();
    //初始化engine  里邊會初始化Engine、Host签舞、Context
    if (engine != null) {
        engine.init();
    }

    // Initialize any Executors 線程池
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }

    // Initialize mapperlistener
    mapperListener.init();

    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            try {
               //初始化connector
                connector.init();
            } catch (Exception e) {
               if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                    throw new LifecycleException(message);
            }
        }
    }
}

初始化Engine秕脓、Executor就追蹤了直接觀察初始化Connector方法。

Connector#initInernal方法

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // 創(chuàng)建一個CoyoteAdapter的適配器
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);

    // 確保 parseBodyMethodsSet has a default
    if (null == parseBodyMethodsSet) {
        setParseBodyMethods(getParseBodyMethods());
    }
    //判斷AprLifecycleListener不可用拋出異常
    if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
        throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
                getProtocolHandlerClassName()));
    }

    if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
            protocolHandler instanceof AbstractHttp11JsseProtocol) {
        AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
                (AbstractHttp11JsseProtocol<?>) protocolHandler;
        if (jsseProtocolHandler.isSSLEnabled() &&
                jsseProtocolHandler.getSslImplementationName() == null) {
            // OpenSSL is compatible with the JSSE configuration, so use it if APR is available
            jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
        }
    }

    try {
       //重點來看protocolHandler初始化方法
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException(
 sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }
}

調(diào)用AbstracProtocol中的初始化方法

public void init() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
    }

    if (oname == null) {
        // Component not pre-registered so register it
        oname = createObjectName();
        if (oname != null) {
            Registry.getRegistry(null, null).registerComponent(this, oname, null);
        }
    }

    if (this.domain != null) {
        rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
        Registry.getRegistry(null, null).registerComponent(
                getHandler().getGlobal(), rgOname, null);
    }

    String endpointName = getName();
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);
    //初始化endpoint的init方法
    endpoint.init();
}

調(diào)用父類的AbstractEndpoing的init方法

public void init() throws Exception {
    if (bindOnInit) {
        //綁定端口
        bind();
        bindState = BindState.BOUND_ON_INIT;
    }
  //注冊一些socketProperties
    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);
        }
    }
}

默認調(diào)用NIOEndpoint的bind方法

public void bind() throws Exception {
   //配置Socket的端口號和一些屬性
    if (!getUseInheritedChannel()) {
       //創(chuàng)建一個serverSocket
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?
    new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        //綁定端口號 
        serverSock.socket().bind(addr,getAcceptCount());
    } else {
        // Retrieve the channel provided by the OS
        Channel ic = System.inheritedChannel();
        if (ic instanceof ServerSocketChannel) {
            serverSock = (ServerSocketChannel) ic;
        }
        if (serverSock == null) {
            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
        }
    }
    serverSock.configureBlocking(true); //mimic APR behavior

    // Initialize thread count defaults for acceptor, poller
    if (acceptorThreadCount == 0) {
        // FIXME: Doesn't seem to work that well with multiple accept threads
 acceptorThreadCount = 1;
    }
    if (pollerThreadCount <= 0) {
        //minimum one poller thread
        pollerThreadCount = 1;
    }
    setStopLatch(new CountDownLatch(pollerThreadCount));

    // Initialize SSL if needed
    initialiseSsl();

    selectorPool.open();
}

總結(jié) :加載流程將所有的各個組件都進行創(chuàng)建和初始化配置儒搭。

2吠架、啟動流程

BootStrap.Start方法

public void start() throws Exception {
   //如果Catalina實例為null在從新創(chuàng)建
    if (catalinaDaemon == null) {
        init();
    }
    //通過反射的方法調(diào)用Catalina的start方法
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
    method.invoke(catalinaDaemon, (Object [])null);
}

Catalina#Start方法

public void start() {
    //校驗server為null從新賦值
    if (getServer() == null) {
        load();
    }
    if (getServer() == null) {
         return;
    }
    //計時
    long t1 = System.nanoTime();

    // Start the new server
    try {
        //調(diào)用StandardServer的start方法 
        getServer().start();
    } catch (LifecycleException e) {
 log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            //出現(xiàn)異常優(yōu)雅關(guān)閉
            getServer().destroy();
        } catch (LifecycleException e1) {
 log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }
    //打印計時
    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
 log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    }

    // Register shutdown hook  注冊一個關(guān)機鉤子  進行優(yōu)雅關(guān)機
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        // If JULI is being used, disable JULI's shutdown hook since
        // shutdown hooks run in parallel and log messages may be lost
        // if JULI's hook completes before the CatalinaShutdownHook()
        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }
    //等候  默認為false 當調(diào)用關(guān)機的時候會執(zhí)行這些方法進行優(yōu)雅關(guān)機
    if (await) {
        //調(diào)用的是StanardServer#Await 將socket進行監(jiān)聽
        await();
        //關(guān)閉所有進程并且銷毀容器
        stop();
    }
}

StandardServer#startInternal方法

protected void startInternal() throws LifecycleException {
    //出發(fā)監(jiān)聽的事件
    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);
    //啟動一些namingResource
    globalNamingResources.start();

    // Start our defined Services
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
    //調(diào)用StandardService的startInternal方法
            services[i].start();
        }
    }
}

StandardService#startInternal

protected void startInternal() throws LifecycleException {
    setState(LifecycleState.STARTING);
    // Start our defined Container first
    if (engine != null) {
        synchronized (engine) {
    //engineStart方法  會調(diào)用StandardEngine#startInternal方法 在方法內(nèi)部調(diào)用父類的ContainerBase#startInternal
            ①engine.start();
        }
    }

    synchronized (executors) {
        for (Executor executor: executors) {
    //啟動線程池這一步在初始化的時候已經(jīng)啟動了在這里不會被啟動了
            executor.start();
        }
    }

    mapperListener.start();

    // Start our defined Connectors second
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            try {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
           //啟動連接器
                    ②connector.start();
                }
            } catch (Exception e) {}
        }
    }
}

①engine.start()

會調(diào)StandardEngine的startInernal里邊會調(diào)用父類ContainerBase#startInternal的方法

父類ContainerBase#startInternal的方法

protected synchronized void startInternal() throws LifecycleException {

    // Start our subordinate components, if any
    logger = null;
    getLogger();
   //獲取集群模式下進行啟動
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    //啟動Realm 認證用戶的作用
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // 找到所有的子類也就是StanardHost
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
      //通過線程池異步啟動子類Host Context Wrapper
      /**
       *  StartChild里邊找到所有子類也就是Host去啟動,調(diào)用StandardHost中的startInternal方法啟動之后在調(diào)用父類的
       *    startInternal搂鲫,進一步找子類進行啟動一直找到所有的Servlet被啟動
       */
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }

    MultiThrowable multiThrowable = null;

     //對啟動的結(jié)果進行判斷
    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Throwable e) {
 log.error(sm.getString("containerBase.threadedStartFailed"), e);
            if (multiThrowable == null) {
                multiThrowable = new MultiThrowable();
            }
            multiThrowable.add(e);
        }

    }
    if (multiThrowable != null) {
        throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                multiThrowable.getThrowable());
    }

    // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }

    //設(shè)置啟動狀態(tài)
    setState(LifecycleState.STARTING);

    // Start our thread
    threadStart();
}

②connector.start

調(diào)用的測試Connector#startInternal方法

protected void startInternal() throws LifecycleException {

    // Validate settings before starting
    if (getPort() < 0) {
        throw new LifecycleException(sm.getString(
                "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
    }

    setState(LifecycleState.STARTING);

    try {
        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
 sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}

調(diào)用AbstractProtocol的start方法

public void start() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
    }
    //啟動endpoint
    endpoint.start();

    // Start timeout thread
    asyncTimeout = new AsyncTimeout();
    Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
    int priority = endpoint.getThreadPriority();
    if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
        priority = Thread.NORM_PRIORITY;
    }
    timeoutThread.setPriority(priority);
    timeoutThread.setDaemon(true);
    timeoutThread.start();
}

endpoint.start默認調(diào)用的是NioEndpoint#startInternal方法

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());

        // Create worker collection
        if ( getExecutor() == null ) {
            createExecutor();
        }

        initializeConnectionLatch();

        // Start poller threads  PollerThread是個實現(xiàn)了Runnable的線程類用來處理過來的用戶請求
        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();
        }
        //開啟NIO模式線程監(jiān)聽
        startAcceptorThreads();
    }
}

總結(jié) :啟動流程和加載流程模式差不多诵肛,只不過是設(shè)置了必要的啟動的一些參數(shù)。

4.3Tomcat請求流程分析

Tomcat請求處理流程默穴。 瀏覽器請求

Tomcat進階學(xué)習下篇

Tomcat中采用Mapper組件來映射Engine和Host之間的關(guān)系怔檩。

4.3.1Mapper組件

在Mapper類中有四個內(nèi)部類,

Tomcat進階學(xué)習下篇

MapElement作為基類其中只包含name蓄诽、Object屬性供其他三個類使用薛训。整個Mapper組件就是起了一個映射的作用,能夠通過Engine層層往下查找仑氛。

4.3.2請求處理流程

Tomcat進階學(xué)習下篇

4.3.3準備

創(chuàng)建一個helloworldWeb項目pom中引入servlet并編寫Hello然后將編譯好的項目復(fù)制到源碼中乙埃。進行啟動并訪問。

public class HelloServlet extends HttpServlet {
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {        
    resp.getWriter().write("<p1>hello web!</p1>");
    }
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
        resp.getWriter().write("<p1>hello web!</p1>");
    }
}

放入到源碼工程中锯岖。

Tomcat進階學(xué)習下篇

啟動源碼的tomcat訪問是否能夠看到自己的helloWorld程序

4.3.4請求流程源碼分析

1介袜、入口

在NioEndpoint的startInternal方法中,調(diào)用父類startAcceptorThreads開啟了線程監(jiān)聽出吹。

父類AbstractEndpoint#startAcceptorThreads 方法

protected final void startAcceptorThreads() {
    int count = getAcceptorThreadCount();
    acceptors = new Acceptor[count];

    for (int i = 0; i < count; i++) {
        //創(chuàng)建一個socket監(jiān)聽器
        acceptors[i] = createAcceptor();
        String threadName = getName() + "-Acceptor-" + i;
        acceptors[i].setThreadName(threadName);
        Thread t = new Thread(acceptors[i], threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }
}

NioEndpoint的createAcceptor方法

protected AbstractEndpoint.Acceptor createAcceptor() {
    return new Acceptor();
}

Acceptor繼承圖

Tomcat進階學(xué)習下篇

可以看出來Acceptor屬于一個線程類遇伞,在父類的startAcceptorThreads方法中最后調(diào)用了start直接會執(zhí)行Acceptor中的run方法。

Acceptor#run方法

public void run() {
    int errorDelay = 0;
    // 一直循環(huán)一直到我們關(guān)閉容器會停止捶牢。
    while (running) {
       ...
       try {
            //如果到達了請求連接數(shù)就需要等待鸠珠。
            countUpOrAwaitConnection();

            SocketChannel socket = null;
            try {
                // socket 接收Socket請求
                socket = serverSock.accept();
            } catch (IOException ioe) {
    ...
    }

            // Successful accept, reset the error delay
            errorDelay = 0;

            // Configure the socket
            if (running && !paused) {
                // setSocketOptions() will hand the socket off to
                // 將獲取到請求塞入到當前隊列中  返回false為失敗 會進行重試。
                if (!setSocketOptions(socket)) {
           //close本次socket
                    closeSocket(socket);
                }
            }
    ...
    }
    state = AcceptorState.ENDED;
}

setSocketOptions方法

protected boolean setSocketOptions(SocketChannel socket) {
    // Process the connection
    try {
        //設(shè)置堵塞為flase 因為是同步非阻塞的NIO
        socket.configureBlocking(false);
        Socket sock = socket.socket();
        socketProperties.setProperties(sock);

       //從SynchronizedStack nioChannels獲取到一個nioChannel 
        NioChannel channel = nioChannels.pop();

       //channel為null的時候從新創(chuàng)建一個channel
        if (channel == null) {
            SocketBufferHandler bufhandler = new SocketBufferHandler(
                    socketProperties.getAppReadBufSize(),
                    socketProperties.getAppWriteBufSize(),
                    socketProperties.getDirectBuffer());
            if (isSSLEnabled()) {
                channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);
            } else {
                channel = new NioChannel(socket, bufhandler);
            }
        } else {
    //將socket放入到ioChannel
            channel.setIOChannel(socket);
            channel.reset();//重置順序
        }
        //從poller吃中獲取到一個Poller 并將channel進行通知 
        getPoller0().register(channel);
    } catch (Throwable t) {
        ...
        return false;
    }
    return true;
}

public void register(final NioChannel socket) {
    socket.setPoller(this);
    NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);
    socket.setSocketWrapper(ka);
    ka.setPoller(this);
    ka.setReadTimeout(getSocketProperties().getSoTimeout());
    ka.setWriteTimeout(getSocketProperties().getSoTimeout());
    ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
    ka.setSecure(isSSLEnabled());
    ka.setReadTimeout(getConnectionTimeout());
    ka.setWriteTimeout(getConnectionTimeout());
    PollerEvent r = eventCache.pop();
    ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
    if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
    else r.reset(socket,ka,OP_REGISTER);

    //上邊是包裝一些時間秋麸,這一步添加到事件中 
    addEvent(r);
}
private final SynchronizedQueue<PollerEvent> events =
        new SynchronizedQueue<>();

//添加到事件中
private void addEvent(PollerEvent event) {
    events.offer(event);
    if ( wakeupCounter.incrementAndGet() == 0 ) selector.wakeup();
}

Poller的run方法

一直在循環(huán)去檢查有沒有事件

public void run() {
    // Loop until destroy() is called
    while (true) {
        try {
            if (!close) {
                hasEvents = events();
        ...
             }
            if (close) {
                ...
    }
        } catch (Throwable x) {
            ...
    continue;
        }
        //判斷是否有事件 events是查看隊列中是否存在事件
        if ( keyCount == 0 ) hasEvents = (hasEvents | events());

        Iterator<SelectionKey> iterator =
            keyCount > 0 ? selector.selectedKeys().iterator() : null;
        // Walk through the collection of ready keys and dispatch
        // any active event.
        while (iterator != null && iterator.hasNext()) {
            if (attachment == null) {
                iterator.remove();
            } else {
                iterator.remove();
        //真正開始執(zhí)行的邏輯
                processKey(sk, attachment);
            }
        } 
    } 
}

2渐排、從Endpoin查找到Servlet

Tomcat進階學(xué)習下篇

從是processKey開始分析。因為代碼比較繁瑣直接采用時序圖來分析灸蟆。

總結(jié):Tomcat在執(zhí)行的過程中運用了比較多的模版設(shè)計模式驯耻,方便拓展,也一層一層的往下去委派給下一層,其中最為核心的就是在Mapper組件11-12步將請求參數(shù)解析成Request對象可缚,并且找好了對應(yīng)的Engine孽水、Host、Context城看、Wrapper女气。有空可以在多多看看源碼學(xué)習人家的設(shè)計思路。

5测柠、Tomcat類加載機制

5.1Jvm類加載機制回顧

什么是類加載機制炼鞠?通俗的說將編譯好的class文件加載到j(luò)vm內(nèi)存中的這個過程稱之為類加載過程。這個機制就是類加載機制轰胁。執(zhí)行這個操作的稱之為類加載器(Class Loader)谒主。

5.1.1Jvm內(nèi)置的類加載器

jvm內(nèi)置的類加載器有:引導(dǎo)類加載器、擴展類加載器赃阀、系統(tǒng)類加載器霎肯。

Tomcat進階學(xué)習下篇

類加載器作用引導(dǎo)啟動類加載器BootstrapClassLoaderc++編寫,加載java核?庫 java.,?如rt.jar中的類榛斯,構(gòu)造ExtClassLoader和AppClassLoader擴展類加載器 ExtClassLoaderjava編寫观游,加載擴展庫 JAVA_HOME/lib/ext?錄下的jar中的類,如classpath中的jre 驮俗,javax.或者java.ext.dir指定位置中的類系統(tǒng)類加載器SystemClassLoader/AppClassLoader默認的類加載器懂缕,搜索環(huán)境變量 classpath 中指明的路徑

當然用戶也可以自定義類加載器,直接加載指定路徑下的class文件王凑。

1) ?戶??的類加載器搪柑,把加載請求傳給?加載器,?加載器再傳給其?加載器索烹,?直到加載器樹的頂層工碾。

2)最頂層的類加載器?先針對其特定的位置加載,如果加載不到就轉(zhuǎn)交給?類百姓。

3)如果?直到底層的類加載都沒有加載到渊额,那么就會拋出異常 ClassNotFoundException。

5.1.2雙親委派機制

什么是雙親委派機制瓣戚?

說白類就是在加載某個類時端圈,類加載器?先把這個任務(wù)委托給他的上級類加載器焦读,遞歸這個操作子库,一直到最頂層,如果上級的類加載器沒有加載矗晃,??才會去加載這個類仑嗅。

雙親委派機制的作用?

1)防?重復(fù)加載同?個.class。通過委托去向上?問?問仓技,加載過了鸵贬,就不?再加載?遍。保證數(shù)據(jù)安全脖捻。

2)保證核?.class文件【一般指的是jdk自定的】不能被篡改阔逼。通過委托?式,不會去篡改核?.class地沮,即使篡改也不會去加載嗜浮,即使加載也不會是同?個.class對象了。不同的加載器加載同?個.class也不是同?個.class對象摩疑。這樣保證了class執(zhí)?安全(如果?類加載器先加載危融,那么我們可以寫?些與java.lang包中基礎(chǔ)類同名

的類, 然后再定義?個?類加載器雷袋,這樣整個應(yīng)?使?的基礎(chǔ)類就都變成我們??定義的類了吉殃。)

5.2Tomcat類加載機制

5.2.1Tomcat為啥自定義實現(xiàn)類加載

Tomcat自定義實現(xiàn)了類加載器,并未嚴格按照雙親委派模式的方式進行加載楷怒。那么思考下如果Tomcat嚴格按照雙親委派機制加載類的話會有啥問題蛋勺?

比如 Tomcat中引入了兩個項目只有版本不同比如,hellowolrd-1.0.jar 和 helloworld-2.0.jar那么兩個項目中都存在com.demo.ClassA鸠删,兩個類的因為版本不同所以內(nèi)容可能不同迫卢,如果采取雙親委派機制使用SystemClassLoader的話加載1.0版本之后在去加載2.0版本的時候發(fā)現(xiàn)已經(jīng)加載過了,就不會進行加載了這樣子就會有問題冶共。

5.2.2Tomcat類加載器繼承體系

Tomcat進階學(xué)習下篇

說明:

1)其中引導(dǎo)類加載器和擴展類加載器作用不變

2)系統(tǒng)類加載器正常情況下加載的是 CLASSPATH 下的類乾蛤,但是 Tomcat 的啟動腳本并未使?該變量,?是通過系統(tǒng)類加載器加載tomcat啟動的類捅僵,?如bootstrap.jar家卖,通常在catalina.bat或者catalina.sh中指定。位于bin目錄下庙楚。

3)Common 通?類加載器加載Tomcat依賴的jar包以及應(yīng)?通?的?些類上荡,位于lib目錄下,?如servlet-api.jar

4)Catalina ClassLoader ?于加載服務(wù)器內(nèi)部可?類馒闷,這些類應(yīng)?程序不能訪問酪捡。就是編寫Tomcat的類。

5)Shared ClassLoader ?于加載應(yīng)?程序共享類纳账,這些類服務(wù)器不會依賴逛薇。

6)Webapp ClassLoader,給每個應(yīng)?程序都會配置?個獨???的Webapp ClassLoader疏虫,他?來單獨加載本應(yīng)?程序 /WEB-INF/classes 和 /WEB-INF/lib 下的類永罚。

Tomcat加載部署的項目加載順序

第一步:從 Bootstrap Classloader加載指定的類啤呼。

第二步:如果未加載到,則從 /WEB-INF/classes加載

第三步:如果未加載到呢袱,則從 /WEB-INF/lib/*.jar 加載

第四步:如果未加載到官扣,則依次從 System、Common羞福、Shared 加載(在這最后?步惕蹄,遵從雙親委派機制)

6、Tomcat對Https的支持

6.1Https簡介

百度百科給出的https解釋治专,

Tomcat進階學(xué)習下篇

簡單來說焊唬,http屬于超文本傳輸協(xié)議祷肯,是明文傳輸?shù)墓佳颍瑐鬏敳话踩奖ⅲ琱ttps在傳輸數(shù)據(jù)的時候會對數(shù)據(jù)進行一層加密术健。采用加密的協(xié)議為SSL(Secure Socket Layer)∩斗保現(xiàn)在也衍生出了TLS(Transport Layer Security)協(xié)議月弛。

Http和Https主要的區(qū)別鸥咖?

  1. HTTPS協(xié)議使?時需要到電?商務(wù)認證授權(quán)機構(gòu)(CA)申請SSL證書
  2. HTTP默認使?8080端?符喝,HTTPS默認使?(8)443端?
  3. HTTPS則是具有SSL加密的安全性傳輸協(xié)議谤祖,對數(shù)據(jù)的傳輸進?加密婿滓,效果上相當于HTTP的升級版
  4. HTTP的連接是?狀態(tài)的,不安全的粥喜;HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進?加密傳輸凸主、身份認證的?絡(luò)協(xié)議,?HTTP協(xié)議安全

6.2Https工作原理

Tomcat進階學(xué)習下篇

簡單來說额湘,第一次握手會找到適合當前瀏覽器的加密算法卿吐,并返回一個公鑰給瀏覽器;第二次握手瀏覽器先校驗公鑰證書锋华,然后生成一串隨機數(shù) 拿著公鑰進行加密嗡官,第三握手服務(wù)器通過私鑰進行解密,并拿著私鑰在進行加密返回給客戶端毯焕,客戶端拿著服務(wù)器返回的信息進行hash比對衍腥,如果統(tǒng)一的話就進行數(shù)據(jù)傳輸。

6.3Tomcat配置Https

使用jdk中的keytool生成一個自定義的證書(商用的話找準認證機構(gòu))

打開終端或者Command輸入下邊命令纳猫。

keytool -genkey -alias helloworld -keyalg RSA -keystore helloworld.keystore

Tomcat進階學(xué)習下篇

在conf/server.xml中配置

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" schema="https" secure="true" SSLEnabled="true">
    <SSLHostConfig>
        <Certificate certificateKeystoreFile="D:/helloworld.keystore" <!--自己的證書的目錄-->
                     certificateKeystorePassword="helloworld" type="RSA"/>
    </SSLHostConfig>
</Connector>

啟動Tomcat訪問https:localhost:8443即可訪問婆咸。

7、Tomcat性能優(yōu)化

調(diào)優(yōu)只提供思路芜辕,不同配置調(diào)優(yōu)的數(shù)值是不固定的尚骄。

了解一些基礎(chǔ)概念:

1)響應(yīng)時間:執(zhí)?某個操作的耗時;

2)吞吐量:系統(tǒng)在給定時間內(nèi)能夠?持的事務(wù)數(shù)量物遇,單位為TPS(Transactions PerSecond的縮寫乖仇, 也就是事務(wù)數(shù)/秒憾儒,?個事務(wù)是指?個客戶機向服務(wù)器發(fā)送請求然后服務(wù)器做出反應(yīng)的過程询兴。

7.1Jvm性能調(diào)優(yōu)

Java 虛擬機的運?優(yōu)化主要是內(nèi)存分配和垃圾回收策略的優(yōu)化:

1乃沙、內(nèi)存直接影響服務(wù)的運?效率和吞吐量

2、垃圾回收機制會不同程度地導(dǎo)致程序運?中斷(垃圾回收策略不同诗舰,垃圾回收次數(shù)和回收效率都是不同的)

1警儒、Java 虛擬機內(nèi)存相關(guān)參數(shù)

參數(shù)參數(shù)作?優(yōu)化建議-server啟動Server,以服務(wù)端模式運?服務(wù)端模式建議開啟-Xms最?堆內(nèi)存建議與-Xmx設(shè)置相同-Xmx最?堆內(nèi)存建議設(shè)置為可?內(nèi)存的80%-XX:MetaspaceSize元空間初始值一般不會修改-XX:MaxMetaspaceSize元空間最?內(nèi)存默認?限-XX:NewRatio年輕代和?年代???值眶根,取值為整數(shù)蜀铲,默認為2[2:1]不需要修改-XX:SurvivorRatioEden區(qū)與Survivor區(qū)??的?值,取值為整數(shù)属百,默認為8[8:1:1]不需要修改

Tomcat進階學(xué)習下篇

上圖為CMS和G1的JVM內(nèi)存空間分配记劝。

調(diào)整Demo:

JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"

直接配置到bin/catalina.bat或者catalina.sh目錄下即可。

查看:使用jdk自帶的工具查看

jhsdb jmap -heap -pid xxx【jdk9 之后使用jhsdb】jmap -heap <pid>  打印堆的使用情況

2族扰、垃圾回收策略

垃圾回收性能指標

1厌丑、吞吐量:?作時間(排除GC時間)占總時間的百分?, ?作時間并不僅是程序運?的時間渔呵,還包含內(nèi)存分配時間怒竿。

2、暫停時間:由垃圾回收導(dǎo)致的應(yīng)?程序停?響應(yīng)次數(shù)/時間

垃圾回收器

1扩氢、串?收集器(Serial Collector):單線程執(zhí)?所有的垃圾回收?作耕驰, 適?于單核CPU服務(wù)器

?作進程-----|(單線程)垃圾回收線程進?垃圾收集|---?作進程繼續(xù)

2、并?收集器(Parallel Collector):?稱為吞吐量收集器(關(guān)注吞吐量)录豺, 以并?的?式執(zhí)?年輕代的垃圾回收朦肘, 該?式可以顯著降低垃圾回收的開銷(指多條垃圾收集線程并??作,但此時?戶線程仍然處于等待狀態(tài))双饥。適?于多處理器或多線程硬件上運?的數(shù)據(jù)量較?的應(yīng)?厚骗。

?作進程-----|(多線程)垃圾回收線程進?垃圾收集|---?作進程繼續(xù)

3、并發(fā)收集器(Concurrent Collector):以并發(fā)的?式執(zhí)??部分垃圾回收?作兢哭,以縮短垃圾回收的暫停時間领舰。適?于那些響應(yīng)時間優(yōu)先于吞吐量的應(yīng)?, 因為該收集器雖然最?化了暫停時間(指?戶線程與垃圾收集線程同時執(zhí)?,但不?定是并?的迟螺,可能會交替進?)冲秽, 但是會降低應(yīng)?程序的性能。

4矩父、CMS收集器(Concurrent Mark Sweep Collector):并發(fā)標記清除收集器锉桑, 適?于那些更愿意縮短垃圾回收暫停時間并且負擔的起與垃圾回收共享處理器資源的應(yīng)?。

5窍株、G1收集器(Garbage-First Garbage Collector):適?于?容量內(nèi)存的多核服務(wù)器民轴, 可以在滿?垃圾回收暫停時間?標的同時攻柠, 以最?可能性實現(xiàn)高吞吐量(JDK1.7之后)。

配置啟動參數(shù)

參數(shù)描述-XX:+UseSerialGC啟?串?收集器-XX:+UseParallelGC啟?并?垃圾收集器后裸,配置了該選項瑰钮,那么 -XX:+UseParallelOldGC默認啟?。-XX:+UseParNewGC年輕代采?并?收集器微驶,如果設(shè)置了 -XX:+UseConcMarkSweepGC選項浪谴,?動啟?。-XX:ParallelGCThreads年輕代及?年代垃圾回收使?的線程數(shù)因苹。默認值依賴于JVM使?的CPU個數(shù)苟耻。-XX:+UseConcMarkSweepGC(CMS)對于?年代,啟?CMS垃圾收集器扶檐。 當并?收集器?法滿?應(yīng)?的延遲需求是凶杖,推薦使?CMS或G1收集器。啟?該選項后款筑, -XX:+UseParNewGC?動啟?智蝠。-XX:+UseG1GC?G1收集器。 G1是服務(wù)器類型的收集器醋虏, ?于多核寻咒、?內(nèi)存的機器。它在保持?吞吐量的情況下颈嚼,?概率滿?GC暫停時間的?標毛秘。

在bin/catalina.sh的腳本中 , 追加如下配置 :

JAVA_OPTS="-XX:+UseConcMarkSweepGC"

可以通過jconsle工具的配置概況查看todo。

7.2Tomcat本身配置調(diào)優(yōu)

1阻课、調(diào)整tomcat線程池

Tomcat進階學(xué)習下篇

2叫挟、調(diào)整tomcat的連接器

調(diào)整tomcat/conf/server.xml 中關(guān)于鏈接器的配置可以提升應(yīng)?服務(wù)器的性能。

參數(shù)說明maxConnections最?連接數(shù)限煞,當?shù)竭_該值后抹恳,服務(wù)器接收但不會處理更多的請求, 額外的請求將會阻塞直到連接數(shù)低于maxConnections 署驻》芟祝可通過ulimit -a 查看服務(wù)器限制。對于CPU要求更?(計算密集型)時旺上,建議不要配置過? ; 對于CPU要求不是特別?時瓶蚂,建議配置在2000左右(受服務(wù)器性能影響)。 當然這個需要服務(wù)器硬件的?持maxThreads最?線程數(shù),需要根據(jù)服務(wù)器的硬件情況宣吱,進??個合理的設(shè)置acceptCount最?排隊等待數(shù),當服務(wù)器接收的請求數(shù)量到達maxConnections 窃这,此時Tomcat會將后?的請求,存放在任務(wù)隊列中進?排序征候, acceptCount指的就是任務(wù)隊列中排隊等待的請求數(shù) 杭攻。 ?臺Tomcat的最?的請求處理數(shù)量祟敛,是maxConnections+acceptCount

3、禁? AJP 連接器

Tomcat進階學(xué)習下篇

4兆解、調(diào)整IO模型

Tomcat8之前的版本默認使?BIO(阻塞式IO)馆铁,對于每?個請求都要創(chuàng)建?個線程來處理,不適

合?并發(fā)痪宰;Tomcat8以后的版本默認使?NIO模式(?阻塞式IO)

Tomcat進階學(xué)習下篇

當Tomcat并發(fā)性能有較?要求或者出現(xiàn)瓶頸時叼架,我們可以嘗試使?APR模式畔裕,APR(Apache Portable

Runtime)是從操作系統(tǒng)級別解決異步IO問題衣撬,使?時需要在操作系統(tǒng)上安裝APR和Native(因為APR

原理是使?使?JNI技術(shù)調(diào)?操作系統(tǒng)底層的IO接?)。

5扮饶、動靜分離

可以使?Nginx+Tomcat相結(jié)合的部署?案具练,Nginx負責靜態(tài)資源訪問,Tomcat負責Jsp等動態(tài)資

源訪問處理(因為Tomcat不擅?處理靜態(tài)資源)甜无。

</article>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扛点,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子岂丘,更是在濱河造成了極大的恐慌陵究,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奥帘,死亡現(xiàn)場離奇詭異铜邮,居然都是意外死亡,警方通過查閱死者的電腦和手機寨蹋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門松蒜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人已旧,你說我怎么就攤上這事秸苗。” “怎么了运褪?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵惊楼,是天一觀的道長。 經(jīng)常有香客問我秸讹,道長檀咙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任嗦枢,我火速辦了婚禮攀芯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘文虏。我一直安慰自己侣诺,他們只是感情好殖演,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著年鸳,像睡著了一般趴久。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搔确,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天彼棍,我揣著相機與錄音,去河邊找鬼膳算。 笑死座硕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的涕蜂。 我是一名探鬼主播华匾,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼机隙!你這毒婦竟也來了蜘拉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤有鹿,失蹤者是張志新(化名)和其女友劉穎旭旭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體葱跋,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡持寄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了年局。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片际看。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖矢否,靈堂內(nèi)的尸體忽然破棺而出仲闽,到底是詐尸還是另有隱情,我是刑警寧澤僵朗,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布赖欣,位于F島的核電站,受9級特大地震影響验庙,放射性物質(zhì)發(fā)生泄漏顶吮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一粪薛、第九天 我趴在偏房一處隱蔽的房頂上張望悴了。 院中可真熱鬧,春花似錦、人聲如沸湃交。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搞莺。三九已至息罗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間才沧,已是汗流浹背迈喉。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留温圆,地道東北人挨摸。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像捌木,于是被迫代替她去往敵國和親油坝。 傳聞我的和親對象是個殘疾皇子嫉戚,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容