OkHttp 源碼解析(一):基本流程

簡介

OkHttp 是一款用于 Android 和 Java 的網(wǎng)絡請求庫绘迁,也是目前 Android 中最火的一個網(wǎng)絡庫。OkHttp 有很多的優(yōu)點:

  • 在 HTTP/2 上允許對同一個 host 的請求共同一個 socket
  • 連接池的使用減少請求延遲(如果 HTTP/2 不支持)
  • 透明的 GZIP 壓縮減少數(shù)據(jù)量大小
  • 響應的緩存避免重復的網(wǎng)絡請求

之前寫過一篇 Retrofit 源碼解析卒密,Retrofit 底層其實就是用的 OkHttp 去請求網(wǎng)絡缀台。本文分析 OKHttp 的源碼哮奇,主要是針對一次網(wǎng)絡請求的基本流程睛约,源碼基于 OKHttp-3.8.0

基本用法

下面是 OkHttp 的使用示例:

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
                .url(url)
                .build();
// 同步
Response response = client.newCall(request).execute();
// 異步
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
    }
});

首先是創(chuàng)建一個 OkHttpClient 對象,其實用過的應該知道可以用 new OkHttpClient.Builder().build() 的方式來配置 OkHttpClient 的一些參數(shù)哲身。有了 OkHttpClient 之后辩涝,下面是創(chuàng)建一個 Request 對象,這個對象也是通過 Builder 模式來生成勘天,其中可以配置一些與這條請求相關(guān)的參數(shù)怔揩,其中 url 是必不可少的。在發(fā)送請求的時候脯丝,需要生成一個 Call 對象商膊,Call 代表了一個即將被執(zhí)行的請求。如果是同步請求宠进,調(diào)用 execute 方法晕拆。異步則調(diào)用 enqueue,并設定一個回調(diào)對象 Callback材蹬。

下面就一步步分析發(fā)送一條網(wǎng)絡請求的基本流程实幕。

OkHttpClient、Request 及 Call 的創(chuàng)建

OkHttpClient 的創(chuàng)建采用了 Builder 模式堤器,可以配置 Interceptor茬缩、Cache 等『鹁桑可以設置的參數(shù)很多凰锡,其中部分參數(shù)如下:

  final Dispatcher dispatcher;  // 請求的分發(fā)器
  final @Nullable Proxy proxy; // 代理
  final List<Protocol> protocols;  // http協(xié)議
  final List<ConnectionSpec> connectionSpecs; 
  final List<Interceptor> interceptors;
  final List<Interceptor> networkInterceptors;
  final EventListener.Factory eventListenerFactory;
  final ProxySelector proxySelector;
  final CookieJar cookieJar;
  final @Nullable Cache cache;

Request 與 OkHttpClient 的創(chuàng)建類似,也是用了 Buidler 模式圈暗,但是其參數(shù)要少很多:

public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
...
}

參數(shù)的含義都很明確掂为,即使 Http 協(xié)議的url、header员串、method 以及 body 部分勇哗。變量 tag 用于標識一條 Request,可用于發(fā)送后取消這條請求寸齐。

client.newCall(request) 生成一個 Call 對象欲诺。Call 實際上是一個接口渺鹦,它封裝了 Request,并且用于發(fā)起實際的網(wǎng)絡請求毅厚。下面是 Call 的全部代碼:

public interface Call extends Cloneable {

  Request request();

  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

其中包含了與網(wǎng)絡請求相關(guān)的操作塞颁,包括發(fā)起祠锣、取消等伴网。看一下 OkHttpClient 是如何創(chuàng)建 Call 的:

  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

從代碼可以看到拳氢,實際上是創(chuàng)建了一個 RealCall 對象馋评,它也是 Call 的唯一一個實現(xiàn)類留特。

有了 RealCall 對象后蜕青,就可以發(fā)起網(wǎng)絡請求了右核,可以是同步請求(execute)或者是異步請求(enqueue)贺喝。異步請求涉及到 Dispatcher躏鱼,先從相對簡單的同步請求開始分析染苛。

同步請求

調(diào)用 RealCall#execute() 即是發(fā)起同步請求茶行,代碼如下:

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

首先判斷這條請求是不是已經(jīng)執(zhí)行過,如果是則會拋出異常(一條請求只能執(zhí)行一次怔鳖,重復執(zhí)行可以調(diào)用 Call#clone())结执。接著執(zhí)行了 client.dispatcher().executed(this)献幔,這行代碼是把當前的 Call 加入到 Dispatcher 的一個隊列中蜡感,這個暫時可以忽略郑兴,后面會分析 Dispatcher情连。

下面一行 Response result = getResponseWithInterceptorChain() 是關(guān)鍵虫几,在 getResponseWithInterceptorChain 中真正執(zhí)行了網(wǎng)絡請求并獲得 Response 并返回挽拔。(下一小節(jié)具體分析其中的邏輯)

最后在 finally 中調(diào)用 Dispatcher 的 finished啡氢,從隊列中移除這條請求术裸。

攔截器 Interceptor

getResponseWithInterceptorChain 的代碼如下:

  Response getResponseWithInterceptorChain() throws IOException {
   // Build a full stack of interceptors.
   List<Interceptor> interceptors = new ArrayList<>();
   interceptors.addAll(client.interceptors());
   interceptors.add(retryAndFollowUpInterceptor);
   interceptors.add(new BridgeInterceptor(client.cookieJar()));
   interceptors.add(new CacheInterceptor(client.internalCache()));
   interceptors.add(new ConnectInterceptor(client));
   if (!forWebSocket) {
     interceptors.addAll(client.networkInterceptors());
   }
   interceptors.add(new CallServerInterceptor(forWebSocket));

   Interceptor.Chain chain = new RealInterceptorChain(
       interceptors, null, null, null, 0, originalRequest);
   return chain.proceed(originalRequest);
 }

可以看到辨绊,其中創(chuàng)建了一個 List 用于添加 Interceptor匹表。首先添加的是 client 中的 interceptors默蚌,也就是在創(chuàng)建 OkHttpClient 對象時自定義的 interceptors苇羡,然后依次添加 retryAndFollowUpInterceptor(重試及重定向)锦茁、BridgeInterceptor(請求參數(shù)的添加)码俩、CacheInterceptor(緩存)稿存、ConnectInterceptor(開始連接)瓣履、用戶自定義的 networkinterceptorsCallServerInterceptor(發(fā)送參數(shù)并讀取響應)安聘。從這里可以知道浴韭,OkHttp 默認添加了好幾個 interceptor 用于完成不同的功能念颈。

在研究各個 interceptor 之前榴芳,需要考慮一下如何讓這些攔截器一個接著一個的執(zhí)行?繼續(xù)看上面的代碼歉井,在添加了各種 interceptors 之后哩至,創(chuàng)建了一個 RealInterceptorChain 對象菩貌。(它的構(gòu)造函數(shù)需要的參數(shù)很多虚茶,并且這些參數(shù)涉及到連接池、請求數(shù)據(jù)的發(fā)送等嘹叫。由于這篇文章主要分析 OkHttp 的基本流程,所以暫時略過這部分)RealInterceptorChain 是接口 Chain 的實現(xiàn)類鸣皂,Chain 的意思,其作用是把各個 Interceptor 串起來依次執(zhí)行仰泻。在獲得了 RealInterceptorChain 之后調(diào)用其 proceed 方法集侯,看名字就能知道是讓 Request 請求繼續(xù)執(zhí)行帜消。

下面具體分析 RealInterceptorChain棠枉,它有如下的成員變量:

  private final List<Interceptor> interceptors;  // 攔截器
  private final StreamAllocation streamAllocation; // 流管理器
  private final HttpCodec httpCodec; // http流,發(fā)送請求數(shù)據(jù)并讀取響應數(shù)據(jù)
  private final RealConnection connection;  // scoket的連接
  private final int index; // 當前攔截器的索引
  private final Request request; // 當前的請求
  private int calls; // chain 的 proceed 調(diào)用次數(shù)的記錄

其中 streamAllocation泡挺、httpCodecconnection 都與 socket 連接有關(guān)辈讶,后續(xù)文章再分析÷γǎ看一下 proceed 方法:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    // 如果已經(jīng)有了一個流贱除,確保即將到來的 request 是用它
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    // 如果已經(jīng)有了一個流,確保這是對 call 唯一的調(diào)用
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }


    // Call the next interceptor in the chain.         
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);                           // (1)
    Interceptor interceptor = interceptors.get(index); // (2)
    Response response = interceptor.intercept(next);   // (3)

    // Confirm that the next interceptor made its required call to chain.proceed().                                 
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

剛開始做了一些連接方面的判斷媳溺,需要關(guān)注的是標了(1)蝎困、(2)、(3)的幾行失暂,主要做了以下操作:

  1. 創(chuàng)建新的 RealInterceptorChain决记,其中 index 加1用于標識當前的攔截器
  2. 通過 index 獲取當前的攔截器
  3. 調(diào)用下一個攔截器的 intercept 方法扩借,并把上面生成的新的 RealInterceptorChain 對象 next 傳進去

由之前的 getResponseWithInterceptorChain 方法可以知道嫉到,當前 RealInterceptorChain 的 interceptors 的第一個是 RetryAndFollowUpInterceptor,下面是其 intercept 的代碼:

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
        // 調(diào)用 chain 的 proceed
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        ... // 省略部分代碼韵丑,主要是錯誤重試以及重定向
    }
  }

這個 Interceptor 主要用于出錯重試以及重定向的邏輯陌僵,其中省略了部分代碼题涨。在這個方法當中要關(guān)注的是再次調(diào)用了 chainproceed 方法,這里的 chain 是之前新創(chuàng)建的 next 對象。相當于說通過調(diào)用 Chain#proceed() 將網(wǎng)絡請求推向下一個攔截器(proceed 中會獲取下一個 Interceptor 并調(diào)用其 intercept 方法),并且得到 response 對象溶推,而下一個攔截器也是類似的操作辐赞。于是赘风,多個 interceptors 就通過這種方式串起來依次執(zhí)行鞍历,并且前一個 Interceptor 可以得到后一個 Interceptor 執(zhí)行后的 response 從而進行處理刑枝。

通過不同的 Interceptor,OkHttp 實現(xiàn)了不同的功能饱岸。各個 Inercept 職責分明又不會互相耦合百框,并且可以非常方便的添加 Interceptor,這是 責任鏈 模式的體現(xiàn)慎菲,非常優(yōu)雅的設計〖奚撸現(xiàn)在可以發(fā)現(xiàn) OkHttp 中的攔截器的調(diào)用過程如下圖所示:

攔截器調(diào)用鏈
攔截器調(diào)用鏈

異步請求

相比于同步請求睬棚,異步請求主要是增加了 Dispatcher 的處理抑党。Dispatcher 是請求的分發(fā)器台汇,它有一下的成員變量:

private int maxRequests = 64;   // 最大連接數(shù)
private int maxRequestsPerHost = 5;  // 單個 host 最大連接數(shù)
private @Nullable Runnable idleCallback;   // 空閑時的回調(diào)

/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService; // 線程池

/** Ready async calls in the order they'll be run. */
// 準備執(zhí)行的異步 Call 的隊列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
// 正在執(zhí)行的的異步 Call 的隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
// 正在執(zhí)行的同步 Call 的隊列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

在 Dispatcher 中粱挡,默認支持的最大并發(fā)連接數(shù)是64帚稠,每個 host 最多可以有5個并發(fā)請求翁锡。

下面看一下線程池 executorService 的創(chuàng)建蔓挖。線程池會在兩個地方創(chuàng)建夕土,分別是 Dispatcher 的構(gòu)造函數(shù)或者是 executorService 方法中(如果調(diào)用了默認的構(gòu)造函數(shù)):

// 默認構(gòu)造函數(shù)沒有創(chuàng)建
public Dispatcher() {
}
// 自定義線程池
public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
}
// 如果沒有自定義線程池,則默認創(chuàng)建
public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

Dispatcher 支持自定義的線程池瘟判,否則會默認創(chuàng)建一個怨绣。在生成 OkHttpClient 對象時,默認調(diào)用的是 Dispatcher 無參的構(gòu)造方法拷获。這個默認線程池通過 new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)) 創(chuàng)建篮撑,看上去類似于一個 CachedThreadPool,沒有常駐的 core 線程匆瓜,空閑線程60秒后自動關(guān)閉赢笨。

enqueue

每個 Call 被添加到某一個隊列,如果是同步請求添加到 runningSyncCalls 中:

synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
}

異步請求添加的邏輯如下:

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
}

具體步驟是:

  1. 判斷是否超出總共的最大連接數(shù)以及單個 host 的最大連接數(shù)
  2. 如果沒有則添加到 runningAsyncCalls 并且提交到線程池執(zhí)行
  3. 否則添加到 readyAsyncCalls 等待后續(xù)執(zhí)行

需要注意的是異步請求的 Call 不是原始的 Call驮吱,而是被包裝為 AsyncCall

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }
    ...
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        // 調(diào)用 getResponseWithInterceptorChain
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

AsyncCall 繼承自 NamedRunnable茧妒,它其實就是一個為線程設置了名字的 Runnable,在其 Run 中調(diào)用 execute左冬,所以 AsyncCall 的主要邏輯都寫在 execute 中桐筏。可以看到最終還是調(diào)用了 getResponseWithInterceptorChain 方法拇砰,所以后續(xù)執(zhí)行網(wǎng)絡請求的邏輯是一樣的梅忌。在獲得 response 之后狰腌,就可以調(diào)用 responseCallback 返回最終的信息。

finished

在上面的代碼中牧氮,finally 里面執(zhí)行了 client.dispatcher().finished(this)琼腔,在同步請求 RealCall#execute() 中也有類似的一行代碼。finished 的作用是讓 Dispatcher 從隊列中移除已完成的 Call踱葛,對于異步請求還會從 readyAsyncCalls 中取出等待中的請求提交給線程池展姐。下面是具體代碼:

  /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }
  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      // 異步請求會進入
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }
    private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();
      // 找到一個等待隊列中的 Call,符合連接數(shù)要求時加入 runningAsyncCalls 并提交給線程池執(zhí)行剖毯。
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

有兩個重載的 finished 方法均調(diào)用了另一個 pirvate 的 finished圾笨,區(qū)別在于這個 finished 的最后一個參數(shù) promoteCalls。對于同步請求(參數(shù)為 RealCall) promoteCallsfalse逊谋,而異步請求(參數(shù)為 AsyncCall) promoteCallstrue擂达。 pirvate 的 finished 主要是從隊列中移除 Call,異步請求會執(zhí)行 promoteCalls胶滋。promoteCalls 里面主要是從 readyAsyncCalls 取出一個 Call板鬓,如果滿足最大連接數(shù)的要求,則把這個 Call 加入 runningAsyncCalls 并提交給線程池執(zhí)行究恤。

通過 runningAsyncCallsreadyAsyncCalls俭令,Dispatcher 實現(xiàn)了異步請求的調(diào)度執(zhí)行。這里比較巧妙的方式是在 finally 中去執(zhí)行 readyAsyncCalls 中的請求部宿,避免了 wait/notity 的方式抄腔,避免了代碼的復雜性。

總結(jié)

OkHttp 的基本執(zhí)行流程如下圖所示:

OKHttp 基本流程
OKHttp 基本流程

主要是以下步驟:

  1. OkHttpClient 調(diào)用 newCall 創(chuàng)建 RealCall 對象理张,Call 封裝了 Request赫蛇,代表一條即將執(zhí)行的請求。
  2. 根據(jù)同步還是異步請求分別調(diào)用 RealCallexecuteenqueue 方法雾叭,將Call 加入 Dispatcher 的相應隊列中悟耘。最終,同步或異步請求都會調(diào)用 getResponseWithInterceptorChain织狐。
  3. getResponseWithInterceptorChain 中暂幼,OkHttp 添加用戶自定義以及默認的 inceptors,并用一個 Chain 管理并依次執(zhí)行每個 Interceptor移迫。
  4. 每個 Interceptor 調(diào)用 Chain#proceed() 將請求發(fā)送給下一級的 Inceptor旺嬉,并能通過這個方法獲得下一級 Interceptor 的 Response。所以上圖所示起意,Request 一級級地往下傳遞鹰服,而獲取了網(wǎng)絡的 Response 之后一級級地往上傳遞。

OkHttp中一條網(wǎng)絡請求的基本流程就是這樣,下一篇文章介紹 OkHttp 如何建立連接:OkHttp 源碼解析(二):建立連接悲酷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末套菜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子设易,更是在濱河造成了極大的恐慌逗柴,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顿肺,死亡現(xiàn)場離奇詭異戏溺,居然都是意外死亡,警方通過查閱死者的電腦和手機屠尊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門旷祸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人讼昆,你說我怎么就攤上這事托享。” “怎么了浸赫?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵闰围,是天一觀的道長。 經(jīng)常有香客問我既峡,道長羡榴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任运敢,我火速辦了婚禮校仑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘者冤。我一直安慰自己肤视,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布涉枫。 她就那樣靜靜地躺著,像睡著了一般腐螟。 火紅的嫁衣襯著肌膚如雪愿汰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天乐纸,我揣著相機與錄音衬廷,去河邊找鬼。 笑死汽绢,一個胖子當著我的面吹牛吗跋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼跌宛,長吁一口氣:“原來是場噩夢啊……” “哼酗宋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疆拘,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蜕猫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哎迄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體回右,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年漱挚,在試婚紗的時候發(fā)現(xiàn)自己被綠了翔烁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡旨涝,死狀恐怖租漂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颊糜,我是刑警寧澤哩治,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站衬鱼,受9級特大地震影響业筏,放射性物質(zhì)發(fā)生泄漏阎肝。R本人自食惡果不足惜魁巩,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一乒融、第九天 我趴在偏房一處隱蔽的房頂上張望褐捻。 院中可真熱鬧豪嗽,春花似錦旅掂、人聲如沸张遭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朋沮。三九已至,卻和暖如春缀壤,著一層夾襖步出監(jiān)牢的瞬間樊拓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工塘慕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留筋夏,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓图呢,卻偏偏與公主長得像条篷,于是被迫代替她去往敵國和親骗随。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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