SpringBoot中Tomcat的源碼分析

前言

Spring Boot 版本 2.1.7.RELEASE
本文大致跟蹤了一遍Tomcat的啟動流程和http請求處理流程。
請邊debug源代碼邊看本文,因為很多變量細節在debug時才能清晰的觀察到,而本文是無法全部涵蓋所有細節的。
文中如有錯誤遺漏,請留言指正。

相關文章

SpringBoot啟動流程的源碼分析

正文

1 第一部分的調用棧如下

createWebServer:180, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
onRefresh:153, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:543, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:743, SpringApplication (org.springframework.boot)
refreshContext:390, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)
run:1214, SpringApplication (org.springframework.boot)
run:1203, SpringApplication (org.springframework.boot)
main:13, XXXXXApplication (com.xxx.xxx)  //自定義的函數入口

這一部分主要工作是:

  • 實例化org.apache.catalina.startup.Tomcat
  • Tomcat實例作為構造函數的入參,實例化org.springframework.boot.web.embedded.tomcat.TomcatWebServer
  • TomcatWebServer的構造函數執行的末尾處,調用Tomcat.start()方法啟動Tomcat

大致流程如下:
1.1
首先在org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer()方法中,
ServletWebServerFactory factory = getWebServerFactory();語句通過beanFactory構造出TomcatWebServer的factory實例。

1.2
然后this.webServer = factory.getWebServer(getSelfInitializer());語句通過TomcatWebServer的factory構造TomcatWebServer實例。Tomcat的實例化及啟動流程就在此函數中完成。
代碼實現在org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory類的getWebServer()方法中。

1.2.1
getWebServer()方法的前半部分都是在實例化Tomcat類。其中包括實例化Connector,StandardServer,StandardService,StandardEngine等類,最終組合成Tomcat類。

1.2.2
getWebServer()方法的最后一條語句getTomcatWebServer(tomcat);,將Tomcat實例作為構造函數的參數,構造了TomcatWebServer。而在TomcatWebServer的構造函數的末尾處,initialize();方法會啟動Tomcat
initialize();方法主要做了兩件事

  • this.tomcat.start(); 啟動tomcat
  • startDaemonAwaitThread();
    啟動一條非daemon線程來執行TomcatWebServer.this.tomcat.getServer().await();
    用途如源代碼中的注釋所寫。
    // Unlike Jetty, all Tomcat threads are daemon threads. We create a
    // blocking non-daemon to stop immediate shutdown
    

先來看一下this.tomcat.start();
tomcat.start();內部會初始化并啟動Server,Service,EngineConnectorHttp11NioProtocolNioEndpoint
這個過程中會多次發布Lifecycle中的事件,但默認只有StandardContextlifecycleListeners中有事件監聽者。
StandardContext.startInternal()方法中會調用initializersonStartup()方法,其中包括了ServletWebServerApplicationContext.selfInitialize()方法。
1.2.3 selfInitialize()方法
1.2.3.1
selfInitialize()方法首先將ServletWebServerApplicationContext以attribute的方式注入到當前的ServletContext中。
ServletWebServerApplicationContext功能齊全,可以獲取bean,可以獲取environment,進而獲取property。
所以一種通過靜態方法獲取ApplicationContext的方式是

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
    ServletContext servletContext = servletRequestAttributes.getRequest().getServletContext();
    WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
}


有時候會需要在自定義的Filter中注入Spring維護的bean。如果是使用當前這種通過springboot的方式啟動服務,直接通過注解注入是沒有問題的。
但如果是通過tomcat方式啟動服務,Filter初始化的時候是沒有在Spring的上下文中的。這時候是無法通過注解注入Spring bean依賴的,可以嘗試使用DelegatingFilterProxy這個類來實現。

1.2.3.2
接下來會調動ServletContextInitializerBeans類的構造函數。
在這個構造函數中加載了ServletFilter
加載的方式有兩種
a) addServletContextInitializerBeans方法
從beanFactory中加載ServletContextInitializer接口的類。FilterRegistrationBean等類就實現了這一接口,這種filter的聲明方式會在這個階段被加載進來。
b) addAdaptableBeans()方法
從beanFactory中加載ServletFilter接口的類。
代碼如下

addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());

代碼中的adapter會將ServletFilter封裝成RegistrationBean,這樣就與a方式相同了。

2 第二部分的調用棧如下
這一部分主要工作是:調用TomcatWebServer.start()方法啟動TomcatWebServer

start:195, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
startWebServer:297, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
finishRefresh:163, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:552, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:743, SpringApplication (org.springframework.boot)
refreshContext:390, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)
run:1214, SpringApplication (org.springframework.boot)
run:1203, SpringApplication (org.springframework.boot)
main:13, XXXXXApplication (com.xxx.xxx)  //自定義的函數入口

大致流程如下:

2.1 addPreviouslyRemovedConnectors();
1.2.2TomcatWebServer.initialize()方法中有這樣一段代碼:

Context context = findContext();
context.addLifecycleListener((event) -> {
        if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                // Remove service connectors so that protocol binding doesn't
                // happen when the service is started.
                removeServiceConnectors();
        }
});

這段代碼的意思是在StandardContext中添加Lifecycle事件監聽器,監聽Lifecycle.START_EVENT事件。當接收到此事件時,執行removeServiceConnectors();,執行的目的如注釋所示。
因此,本節的addPreviouslyRemovedConnectors();的目的就是把之前移除的connecters再添加回來。

2.1.1 addPreviouslyRemovedConnectors();方法中使用service.addConnector(connector);方法添加connector。
這一方法曾經在 1.2.1TomcatServletWebServerFactory.getWebServer()方法中被調用過一次,調用處的語句為tomcat.getService().addConnector(connector);
不過在TomcatServletWebServerFactory.getWebServer()方法中被調用時,由于getState().isAvailable() == false,沒能繼續執行后半部分,此時StandardService.state== LifecycleState.NEW;而在本節的addPreviouslyRemovedConnectors();方法中調用時,StandardService.state== LifecycleState.STARTED,可以繼續執行connector.start();

2.1.2 connector.start();
方法中會連續調用,Connector.startInternal()Http11NioProtocol.start()NioEndpoint.start()。而主要工作在NioEndpoint.start()中執行,這里面包含了兩個重要語句
bindWithCleanup();
startInternal();

2.1.2.1 NioEndpoint.bindWithCleanup()

  • 創建并配置ServerSocketChannel
  • Initialize SSL if needed
  • selectorPool.open(getName());
    • NioSelectorPool.sharedSelector = Selector.open();
    • NioSelectorPool.blockingSelector = new NioBlockingSelector();
    • NioSelectorPool.blockingSelector.poller = new BlockPoller();
    • 啟動"BlockPoller"線程,執行
      org.apache.tomcat.util.net.NioBlockingSelector.BlockPoller.run()

2.1.2.2 NioEndpoint.startInternal()
創建executor
啟動"ClientPoller"線程,執行org.apache.tomcat.util.net.NioEndpoint.Poller.run()
啟動"Acceptor"線程,執行org.apache.tomcat.util.net.Acceptor.run()

2.1.2 續
至此,啟動了一些線程總結如下

線程名 執行方法
http-nio-8080-Acceptor org.apache.tomcat.util.net.Acceptor.run()
http-nio-8080-BlockPoller org.apache.tomcat.util.net.NioBlockingSelector.BlockPoller.run()
http-nio-8080-ClientPoller org.apache.tomcat.util.net.NioEndpoint.Poller.run()
http-nio-8080-exec-1 org.apache.tomcat.util.threads.ThreadPoolExecutor (這是executor的worker線程,沒有具體的執行任務)
http-nio-8080-exec-2 同上,共有10條線程

2.2 performDeferredLoadOnStartup();
這里會調用javax.servlet.GenericServlet.init()方法,對org.apache.catalina.servlets.DefaultServlet進行初始化。
org.springframework.web.servlet.DispatcherServlet是延遲初始化的,它會在將來有網絡請求到來時才調用init()

3 tomcat處理網絡請求
3.1 首先總結一下基本信息
3.1.1 線程信息如 2.1.2 續 所述
3.1.2 類信息摘要

class-dig.png

其中
NioEndpoint.poller != NioBlockingSelector.poller
NioSelectorPool.sharedSelector == NioBlockingSelector.sharedSelector == BlockPoller.selector (圖中藍色框的Selector為同一個對象)
NioEndpoint$Poller.selector != NioSelectorPool.sharedSelector (圖中紅色框的Selector與藍色框的Selector不是同一個對象)

3.2 Accept線程
3.2.1
"Acceptor"線程中的org.apache.tomcat.util.net.Acceptor.run()方法在執行的時候,會阻塞在socket = endpoint.serverSocketAccept();語句處。
當接受到新的網絡請求時,此語句解除阻塞,socket得到賦值,繼續執行。

3.2.2
socket獲得賦值后,會來到如下代碼段

// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
if (!endpoint.setSocketOptions(socket)) {
    endpoint.closeSocket(socket);
}

其中endpoint.setSocketOptions(socket)語句內內部,會將socket封裝成NioChannel類,NioChannel類又被進一步封裝成NioSocketWrapper類。
不過NioChannel類和NioSocketWrapper類二者內部都有著對方的引用。代碼摘要如下

NioChannel channel = new NioChannel(socket, bufhandler);
NioSocketWrapper socketWrapper = new NioSocketWrapper(channel, this);
channel.setSocketWrapper(socketWrapper);

接下來org.apache.tomcat.util.net.NioEndpoint.Poller.register()方法將其注冊到Poller中。
Poller.register()方法中又將NioChannel進一步封裝成PollerEvent,然后添加到Poller.events隊列中,隊列的類型是SynchronizedQueue<PollerEvent> events

3.3 ClientPoller線程
此線程主體是一個while(true)輪詢邏輯。
3.3.1
首先調用events();函數處理events隊列中的PollerEvent,當 3.2.2 的末尾處有新PollerEvent加入隊列后,events();方法內部會對其處理,過程如下
3.3.1.1
PollerEvent.run()方法內部,3.2.2 末尾處注冊時的賦值,致使interestOps == OP_REGISTER,進而執行SocketChannel.register()方法,將這個socket注冊到Poller.selector上,監聽SelectionKey.OP_READ,attachment是NioSocketWrapper
3.3.1.2
PollerEvent.reset()方法重置PollerEvent對象。
eventCache.push(pe);將重置后的PollerEvent對象放入cache,方便以后重用。

3.3.2
events();調用完成后,會調用selector.selectedKeys().iterator()3.3.1.1 中注冊到selector中的socket會被select出來。processKey(sk, socketWrapper);方法會對這個socket做進一步處理,其中socketWrapper是當時注冊時的attachment。
processKey(sk, socketWrapper);方法內部會將socketWrapper封裝成NioEndpoint.SocketProcessor類,交給NioEndpointEndpoint.executor去執行,也就是交給worker線程了。

3.4 worker線程
3.4.1
3.3.2 的末尾,worker線程接到新的任務后,執行的是SocketProcessor.doRun()方法。
其中關鍵語句是state = getHandler().process(socketWrapper, event);,即ConnectionHandler.process()方法,這個方法超長。
這個方法內部處理語句是state = processor.process(wrapper, status);,即Http11Processor.process()。然后是層層調用如下,直到執行filter。

doFilter:166, ApplicationFilterChain (org.apache.catalina.core) [1]
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:490, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:408, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:853, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1587, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1130, ThreadPoolExecutor (java.util.concurrent)
run:630, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:832, Thread (java.lang)

org.apache.catalina.core.StandardWrapperValve.invoke()方法中,
servlet = wrapper.allocate();獲取DispatcherServlet
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);獲取filterChain。
org.apache.catalina.core.StandardContext.filterMaps中記錄了所有filter。
filterChain.doFilter(request.getRequest(), response.getResponse());執行filter。

3.4.2 filter的執行
ApplicationFilterChain.doFilter() -> ApplicationFilterChain.internalDoFilter() -> if (pos < n) -> Filter.doFilter() -> ApplicationFilterChain.doFilter()
上述是鏈式的執行所有filter。
當filter全部執行完畢后,if (pos < n)將為false,繼續執行ApplicationFilterChain.internalDoFilter()的后半部分。
最終執行到servlet.service(request, response);,servlet
DispatcherServlet

3.4.3 DispatcherServlet.doDispatch()
DispatcherServlet.getHandler()會根據請求url尋找對應controller。
AbstractHandlerMapping.getHandlerExecutionChain()會添加intercepter。
最終返回HandlerExecutionChain
HandlerExecutionChain的執行順序如下:

  • mappedHandler.applyPreHandle()調用HandlerInterceptor.preHandle()
  • ha.handle()調用controller內的執行方法,這個方法是被@Aspect修飾過的。
  • mappedHandler.applyPostHandle()調用HandlerInterceptor.postHandle()
  • mappedHandler.triggerAfterCompletion()調用HandlerInterceptor.afterCompletion()

3.4.3 ha.handle()中會調用ServletInvocableHandlerMethod.invokeAndHandle()方法。
該方法中的Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);會最終調用controller的方法,并獲得返回值。
然后this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest);會將controller的返回值轉化成response body,并分片發送response。
分片發送依次調用
CoyoteOutputStream.flush()
org.apache.coyote.Response.action() actionCode == ActionCode.CLIENT_FLUSH
Http11Processor.action()
NioEndpoint.NioSocketWrapper.doWrite()
NioSelectorPool.write()
NioBlockingSelector.write()

然后再經過層層filter以及其他操作,最終回到了org.apache.catalina.connector.CoyoteAdapter.service()方法,該方法中會執行以下語句

request.finishRequest();
response.finishResponse();

org.apache.catalina.connector.Response.finishResponse()中會調用coyoteResponse.action(ActionCode.CLOSE, null); -> org.apache.coyote.http11.Http11Processor.finishResponse()發送結束分片。http response至此結束。

4 Tomcat 線程池
Tomcat基于jdk中提供的線程池,實現了自己的線程池。
4.1
首先梳理一下jdk中的線程池運行方式。
java.util.concurrent.ThreadPoolExecutor的構造方式中有3個重要參數 corePoolSize, maximumPoolSize, workQueue
我們以java.util.concurrent.LinkedBlockingQueue類作為workQueue舉例說明。
此線程池構造完畢后,線程池內初始有 0 條線程。然后開始提交任務。
如果 當前線程數 < corePoolSize,建立一條新線程執行任務。
如果 當前線程數 == corePoolSize,將任務放入workQueue隊列等待執行。
如果 當前線程數 == corePoolSizeworkQueue滿了,建立一條新線程執行任務。
如果 workQueue滿了 且 當前線程數 == maximumPoolSize,執行reject()方法,拒絕新任務,拒絕新任務的處理方式默認是拋出RejectedExecutionException異常。

4.2
Tomcat的線程池是在org.apache.tomcat.util.net.AbstractEndpoint.createExecutor()方法中執行創建的。
org.apache.tomcat.util.threads.ThreadPoolExecutor類繼承了java.util.concurrent.ThreadPoolExecutor
Tomcat 使用的隊列類為 org.apache.tomcat.util.threads.TaskQueue,該類繼承了java.util.concurrent.LinkedBlockingQueue<Runnable>,且capacity == Integer.MAX_VALUE,即隊列承載力極大。
他們組合使用構成的線程池的運行方式如下:
此線程池構造完畢后,線程池內初始有 corePoolSize 條線程。然后開始提交任務。
由于初始化時就建立了一些線程,所以最初提交的幾個任務可以直接被執行。
如果 當前線程數 == corePoolSize 且 沒有空閑線程,建立一條新線程執行任務。
如果 當前線程數 == maximumPoolSize,將任務放入workQueue隊列等待執行。
如果 workQueue滿了 且 當前線程數 == maximumPoolSize,執行reject()方法,拒絕新任務,拒絕新任務的處理方式默認是拋出RejectedExecutionException異常。

4.3
對比上述二者的執行方式,可知其間的差異。
jdk自帶的線程池,初始化時不會啟動新線程,當有任務提交時才會啟動線程。當活躍線程達到corePoolSize時,會優先將任務放入隊列等待,當等待隊列滿時,才會再次新增線程數量,直到達到maximumPoolSize
而Tomcat使用的線程池,初始化時就會啟動corePoolSize條線程。如果線程都在忙于處理任務時,再提交新任務,會繼續創建新線程,直到達到maximumPoolSize條線程。如果線程都在忙于處理任務,且線程數量達到了上限時,再提交新任務,才會將其放入等待隊列。
可見,二者的顯著區分處有三點。

  • jdk線程池初始化時不會啟動任何線程,第一個任務提交時才會創建線程。
    而Tomcat線程池初始化就會啟動corePoolSize條線程。
  • 在活躍線程達到corePoolSize,且所有線程都在忙碌中(即都在執行任務)時,再次提交新任務時,二者對新任務的處理方式的優先級不同。
    jdk線程池會優先將新任務放入任務隊列中等待執行,當任務隊列滿了才會再次新建線程。
    而Tomcat線程池會優先創建線程執行任務,但線程數量達到上限且都在忙碌執行時,才會將任務放入等待隊列中。
  • jdk線程池中的任務隊列可自由使用任意類。
    而Tomcat線程池需要搭配使用TaskQueue類,該類的capacity == Integer.MAX_VALUE

所以Tomcat的線程池的策略是盡可能用線程立即執行到來的新任務,達到線程數量上限才會讓任務排隊等待。Tomcat的任務隊列的承載能力又是極大的,capacity == Integer.MAX_VALUE

4.4
tomcat線程池創建的調用棧

<init>:77, ThreadPoolExecutor (org.apache.tomcat.util.threads)
createExecutor:987, AbstractEndpoint (org.apache.tomcat.util.net)
startInternal:331, NioEndpoint (org.apache.tomcat.util.net)
start:1293, AbstractEndpoint (org.apache.tomcat.util.net)
start:614, AbstractProtocol (org.apache.coyote)
startInternal:1072, Connector (org.apache.catalina.connector)
start:183, LifecycleBase (org.apache.catalina.util)
addConnector:239, StandardService (org.apache.catalina.core)
addPreviouslyRemovedConnectors:282, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
start:213, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
start:43, WebServerStartStopLifecycle (org.springframework.boot.web.servlet.context)
doStart:182, DefaultLifecycleProcessor (org.springframework.context.support)
access$200:53, DefaultLifecycleProcessor (org.springframework.context.support)
start:360, DefaultLifecycleProcessor$LifecycleGroup (org.springframework.context.support)
startBeans:158, DefaultLifecycleProcessor (org.springframework.context.support)
onRefresh:122, DefaultLifecycleProcessor (org.springframework.context.support)
finishRefresh:895, AbstractApplicationContext (org.springframework.context.support)
refresh:554, AbstractApplicationContext (org.springframework.context.support)
refresh:143, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:755, SpringApplication (org.springframework.boot)
refresh:747, SpringApplication (org.springframework.boot)
refreshContext:402, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)
run:1247, SpringApplication (org.springframework.boot)
run:1236, SpringApplication (org.springframework.boot)
main:18, Application (com.jindi.search.platform)

5 其他
5.1 http status code
HttpServletResponse類中,定義了多個http狀態碼的常量,可以用來反向查找,查看在哪些情況下會返回哪種狀態碼。

推薦閱讀

Tomcat剖析之架構篇(一)
Tomcat剖析之源碼篇(二)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373