Binder深入理解及與AIDL的使用和理解

Binder深入理解

  1. 背景知識

    • 進程隔離:進程隔離是為保護操作系統(tǒng)中進程互不干擾而設計的一組不同硬件和軟件的技術雨席。這個技術是為了避免進程A寫入進程B的情況發(fā)生。進程隔離的實現(xiàn)捌显,使用了虛擬機地址空間茁彭。進程A的虛擬地址和進程B的虛擬地址不同,這樣就防止進程A將數(shù)據(jù)寫入進程B扶歪。

      操作系統(tǒng)不同進程之間理肺,數(shù)據(jù)不共享。因此一個進程要與另外一個進程通信,需要某種機制才能完成妹萨。

    • 用戶空間/內(nèi)核空間:Linux內(nèi)核是操作系統(tǒng)的核心年枕,所以為了它的安全,不可能允許其他的應用程序隨便調(diào)用或訪問乎完。所以它提供了一個保護機制熏兄,這個保護機制把內(nèi)核和上層應用程序抽象的隔離開,分別為內(nèi)核空間和用戶空間囱怕。

    • 內(nèi)核態(tài)/用戶態(tài):用戶空間訪問內(nèi)核空間的唯一方式是系統(tǒng)調(diào)用霍弹,這是讓用戶空間對內(nèi)核空間的訪問通過一個統(tǒng)一的入口,這樣所有的資源(如訪問文件)訪問都在內(nèi)核的控制下娃弓,以免用戶程序?qū)ο到y(tǒng)資源的越權訪問典格,從而保障了系統(tǒng)的安全和穩(wěn)定。當一個任務(進程)執(zhí)行系統(tǒng)調(diào)用而陷入內(nèi)核代碼中執(zhí)行時台丛,就稱之為內(nèi)核態(tài)耍缴。當進程在執(zhí)行用戶自己的代碼時,則稱之為用戶態(tài)挽霉。

    • 內(nèi)核模塊/驅(qū)動:通過系統(tǒng)調(diào)用用戶空間可以訪問內(nèi)核空間防嗡,但是用戶空間之間的相互通信還是不行的,那么就需要系統(tǒng)內(nèi)核的支持侠坎,如Socket,管道等都是內(nèi)核支持的蚁趁。但是Binder并不是Linux內(nèi)核的一部分。需要Linux的動態(tài)可加載內(nèi)核模塊來解決這個問題实胸。用戶進程之間通過這個模塊作為橋梁他嫡,就可以相互通信了。在Android系統(tǒng)中庐完,這個運行在內(nèi)核空間钢属,負責各個用戶進程通過Binder通信的模塊叫做Binder驅(qū)動。

      動態(tài)可加載內(nèi)核是指模塊可以在系統(tǒng)運行期間加載到內(nèi)核或從內(nèi)核卸載门躯。模塊是具有獨立功能的程序淆党,它可以被單獨編譯,但不能獨立運行讶凉。它在運行時被鏈接到內(nèi)核作為內(nèi)核的一部分在內(nèi)核空間中運行染乌。模塊通常由一組函數(shù)和數(shù)據(jù)結構組成。

  2. 使用Binder的原因:

    • 穩(wěn)定性:Binder采用C/S的通信模式懂讯,客戶端有什么需求直接交給服務端去完成慕匠,架構清晰,職責明確又相互獨立域醇,自然穩(wěn)定性更好台谊。在Linux通信機制中蓉媳,目前只有socket支持C/S的通信模式,但是Socket有其劣勢
    • 高效性:Socket是一個通用接口锅铅,導致其傳輸效率低酪呻,開銷大;管道和消息隊列因為采用存儲轉發(fā)方式盐须,所以至少需要拷貝2次數(shù)據(jù)玩荠,效率低;共享內(nèi)存雖然傳輸時沒有拷貝數(shù)據(jù)贼邓,不是C/S模式阶冈,控制機制復雜。Binder只拷貝一次
    • 安全性:Linux的IPC機制在本身的實現(xiàn)中塑径,并沒有安全措施女坑,傳統(tǒng)的進程通訊方式對與通信雙方的身份并沒有嚴格的驗證,只有在上層協(xié)議上進行架設统舀,比如Socket通信IP地址是客戶端手動填入的匆骗,可以進行偽造。而Binder機制的UID/PID是由Binder機制本身在內(nèi)核空間添加身份標識誉简,因而大大提高了安全性碉就。
  3. Binder的通信模型:

    Binder采用C/S通信模式。那么通信的2端可以分為Server進程闷串,Client進程瓮钥。由于進程隔離,它們之間沒法直接進行通信烹吵。需要一定的機制幫助他們進行通信碉熄。

    假如將2端之間的通訊看成A給B打電話。那么首先A要知道B的電話號碼年叮,其次撥號以后需要基站將信息傳播出去。那么將有4個角色玻募,分別是發(fā)起通話的Client只损,通訊錄ServerManager(SM),基站Binder驅(qū)動驅(qū)動,接電話的Server七咧。那么整個通訊過程如下:

    • 建立通訊錄ServerManager跃惫。首先有一個進程向驅(qū)動提出申請為SM,驅(qū)動同意后艾栋,SM進程負責管理Service爆存。這時還沒有任何Server進行注冊,所以通訊錄還是空的
    • 各個Server向SM進行注冊蝗砾。每個Server端啟動后先较,在SM進行注冊携冤,SM會建立一張表,并把這些Server的名字和地址存入其中闲勺。
    • Client要與某個Server進行通訊曾棕,那么會去SM里查詢這個Server的地址,然后開始通訊菜循。

    以上就是進程間通訊的簡單過程翘地,Server端將自己注冊到SM中,Client要與哪個Server進行通訊癌幕,就去SM里找對應的地址衙耕,這時打電話的雙方號碼都有了,就差一個基站幫忙發(fā)送信息了勺远。這個基站就是Binder驅(qū)動橙喘,它的原理是整個跨進程通訊的核心。

  4. Binder機制跨進程原理

    上面分析了Binder的通訊過程谚中,建立聯(lián)系后通訊由Binder驅(qū)動來完成渴杆,那么再來看Binder驅(qū)動完成通訊的過程。

    假如Client進程要調(diào)用Server進程的Object對象的一個方法get(),那么具體過程如下:

    • Server進程向SM進行注冊宪塔,告訴SM它的地址磁奖,及它有一個Object對象,可以執(zhí)行get()操作

    • Client向SM進行查詢Server的Object對象某筐。這時Binder驅(qū)動并不會返回一個真正的Object對象比搭,而是返回一個代理對象ObjectProxy。這時連接已經(jīng)建立南誊,然后準備開始通訊身诺。Client進程拿到ObjectProxy后調(diào)用它的get(),這時Binder驅(qū)動收到這個調(diào)用請求,發(fā)現(xiàn)ObjectProxy是Server的Object代理抄囚,于是Binder驅(qū)動通知Server進程調(diào)用它的get(),Server收到消息后調(diào)用自己的get()并把結果返回給Binder驅(qū)動霉赡,Binder驅(qū)動再把結果返回給Client。

      可以看出Binder跨進程實際并不是真正把一個對象傳輸?shù)搅肆硗庖粋€進程幔托。Client只是對代理對象的操作穴亏,然后Binder驅(qū)動又把對代理對象的操作告訴Server,讓Server執(zhí)行真正的操作重挑,并把結果返回給Binder驅(qū)動嗓化,Binder驅(qū)動再通過代理對象把結果返回給Client。

SM和Server通常也不在一個進程谬哀,所以Server注冊的過程其實也是跨進程通訊刺覆。SM內(nèi)部已經(jīng)有一個默認地址,Server通過這個默認地址查找到SM史煎。Server存在SM里的對象其實也是一個代理對象谦屑。Server的本地對象只有一個驳糯,其他都是代理對象。

  1. 總結
    • Binder是一種通訊機制伦仍,ALDL使用的就是Binder進行通訊
    • 對于Server來說Binder指的是Binder本地對象
    • 對于Client來說结窘,Binder指的是Binder代理對象,它只是Binder本地對象的一個遠程代理充蓝。對于這個代理對象的操作隧枫,最終會通過Binder驅(qū)動轉發(fā)到Binder本地對象上完成。
    • 對于傳輸過程來說谓苟,Binder驅(qū)動會對代理對象和本地對象進行自動轉換處理
    • 要完成本地Binder和代理BInder的轉換官脓,那么Binder驅(qū)動里必然保存了每一個跨進程Binder對象的相關信息,在Binder驅(qū)動中涝焙,Binder本地對象的代表是binder_node的數(shù)據(jù)結構卑笨,Binder代理對象是用binder_ref代表的

AIDL

Java為了方便進行進程進程間通訊,對Binder的通訊過程進行了封裝仑撞,這就是AIDL赤兴,

AIDL的使用

  1. Server端:

    1. 定義Server端的aidl接口,此接口主要定義可以對外提供的服務

      // IServer.aidl
      package com.madnessxiong.server;
      interface IServer {
          void get();
      }
      
    2. 編譯生成aidl接口實現(xiàn)文件隧哮,這一步主要是生成Binder對象桶良。

      /*
       * This file is auto-generated.  DO NOT MODIFY.
       * Original file: /work/project/Server/app/src/main/aidl/com/madnessxiong/server/IServer.aidl
       */
      package com.madnessxiong.server;
      // Declare any non-default types here with import statements
      
      public interface IServer extends android.os.IInterface
      {
      /** Local-side IPC implementation stub class. */
      public static abstract class Stub extends android.os.Binder implements com.madnessxiong.server.IServer
      {
      private static final java.lang.String DESCRIPTOR = "com.madnessxiong.server.IServer";
      /** Construct the stub at attach it to the interface. */
      public Stub()
      {
      this.attachInterface(this, DESCRIPTOR);
      }
      /**
       * Cast an IBinder object into an com.madnessxiong.server.IServer interface,
       * generating a proxy if needed.
       */
      public static com.madnessxiong.server.IServer asInterface(android.os.IBinder obj)
      {
      if ((obj==null)) {
      return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.madnessxiong.server.IServer))) {
      return ((com.madnessxiong.server.IServer)iin);
      }
      return new com.madnessxiong.server.IServer.Stub.Proxy(obj);
      }
      @Override public android.os.IBinder asBinder()
      {
      return this;
      }
      @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
      {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
      case INTERFACE_TRANSACTION:
      {
      reply.writeString(descriptor);
      return true;
      }
      case TRANSACTION_get:
      {
      data.enforceInterface(descriptor);
      this.get();
      reply.writeNoException();
      return true;
      }
      default:
      {
      return super.onTransact(code, data, reply, flags);
      }
      }
      }
      private static class Proxy implements com.madnessxiong.server.IServer
      {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
      mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
      return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
      return DESCRIPTOR;
      }
      @Override public void get() throws android.os.RemoteException
      {
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try {
      _data.writeInterfaceToken(DESCRIPTOR);
      mRemote.transact(Stub.TRANSACTION_get, _data, _reply, 0);
      _reply.readException();
      }
      finally {
      _reply.recycle();
      _data.recycle();
      }
      }
      }
      static final int TRANSACTION_get = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
      }
      public void get() throws android.os.RemoteException;
      }
      
      
      
    3. 創(chuàng)建Service,在Service中返回IBinder對象,這個IBinder是系統(tǒng)生成的IServer.Stub()沮翔。

      public class ClientService extends Service {
      
          @Override
          public IBinder onBind(Intent intent) {
              // TODO: Return the communication channel to the service.
              return  iBinder;
          }
      
         IBinder iBinder= new IServer.Stub(){
             @Override
             public void get() throws RemoteException {
      
             }
         };
      }
      
      //在manifests注冊service
              <service
                  android:name=".ClientService"
                  android:enabled="true"
                  android:exported="true">
                  <intent-filter>
                      <action android:name="com.madnessxiong.server.server" />
                  </intent-filter>
              </service>
      
    4. 啟動Service提供服務

          Intent intent = new Intent(this, ClientService.class);
          startService(intent);
      
  2. Client端:

    1. 將Server端的aidl文件復制到Client端,包名要和Server端保持一致

      interface IServer {
          void get();
      }
      
    2. 編譯陨帆,生成aidl實現(xiàn)接口文件

      /*
       * This file is auto-generated.  DO NOT MODIFY.
       * Original file: /work/project/Client/app/src/main/aidl/com/madnessxiong/server/IServer.aidl
       */
      package com.madnessxiong.server;
      // Declare any non-default types here with import statements
      
      public interface IServer extends android.os.IInterface
      {
      /** Local-side IPC implementation stub class. */
      public static abstract class Stub extends android.os.Binder implements com.madnessxiong.server.IServer
      {
      private static final java.lang.String DESCRIPTOR = "com.madnessxiong.server.IServer";
      /** Construct the stub at attach it to the interface. */
      public Stub()
      {
      this.attachInterface(this, DESCRIPTOR);
      }
      /**
       * Cast an IBinder object into an com.madnessxiong.server.IServer interface,
       * generating a proxy if needed.
       */
      public static com.madnessxiong.server.IServer asInterface(android.os.IBinder obj)
      {
      if ((obj==null)) {
      return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.madnessxiong.server.IServer))) {
      return ((com.madnessxiong.server.IServer)iin);
      }
      return new com.madnessxiong.server.IServer.Stub.Proxy(obj);
      }
      @Override public android.os.IBinder asBinder()
      {
      return this;
      }
      @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
      {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
      case INTERFACE_TRANSACTION:
      {
      reply.writeString(descriptor);
      return true;
      }
      case TRANSACTION_get:
      {
      data.enforceInterface(descriptor);
      this.get();
      reply.writeNoException();
      return true;
      }
      default:
      {
      return super.onTransact(code, data, reply, flags);
      }
      }
      }
      private static class Proxy implements com.madnessxiong.server.IServer
      {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
      mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
      return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
      return DESCRIPTOR;
      }
      @Override public void get() throws android.os.RemoteException
      {
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try {
      _data.writeInterfaceToken(DESCRIPTOR);
      mRemote.transact(Stub.TRANSACTION_get, _data, _reply, 0);
      _reply.readException();
      }
      finally {
      _reply.recycle();
      _data.recycle();
      }
      }
      }
      static final int TRANSACTION_get = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
      }
      public void get() throws android.os.RemoteException;
      }
      
      
    3. 建立ServiceConnection

      public class ClientConnection implements ServiceConnection {
      
          private IServer iServer;
      
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              iServer = IServer.Stub.asInterface(service);
          }
      
          @Override
          public void onServiceDisconnected(ComponentName name) {
      
          }
      
          public IServer getiServer() {
              return iServer;
          }
      }
      

      在onServiceConnected使用 IServer.Stub.asInterface返回IBinder代理對象。

    4. 綁定服務采蚀,建立連接

          public void bindService() {
              Intent intent = new Intent();
              intent.setAction("com.madnessxiong.server.server");
              intent.setPackage("com.madnessxiong.server");
              clientConnection = new ClientConnection();
              bindService(intent, clientConnection, Context.BIND_AUTO_CREATE);
          }
      
    5. 開始跨進程通訊

      clientConnection.getiServer().get();
      

    以上就是簡單的AIDL的整個通信過程疲牵。

AIDL原理分析:

根據(jù)Binder原理分析,需要4個角色榆鼠,Server端纲爸,本地Binder對象,Client端妆够,代理Binder對象识啦。

  1. Server端是提供服務的進程,那么再看下一它的本地Binder對象

       IBinder iBinder= new IServer.Stub(){
           @Override
           public void get() throws RemoteException {
           }
       };
    

    可以看到是通過IServer.Stub返回的责静,這個是編譯后自動生成的袁滥,看一下它的實現(xiàn):

    public static abstract class Stub extends android.os.Binder implements com.madnessxiong.server.IServer {
        }
    

    可以看到Stub繼承自Binder盖桥,意味著Stub是一個本地Binder對象灾螃。也就是它具有進程間通訊的能力。同時實現(xiàn)了IServer接口揩徊,這個接口代表能為客戶端提供的服務窟哺。

  2. Client是調(diào)用服務的進程搓劫,那么看一下它的代理Binder對象

    iServer = IServer.Stub.asInterface(service);
    

    可以看到是通過asInterface()獲得本地代理Binder對象的叫榕,看一下它的實現(xiàn)

            public static com.madnessxiong.server.IServer asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                 //嘗試查找本地代理對象
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                 //根據(jù)本地代理對象是否存在,判斷是否是當前進程姜挺,如果是當前進程則返回本地Binder對象
                if (((iin != null) && (iin instanceof com.madnessxiong.server.IServer))) {
                    return ((com.madnessxiong.server.IServer) iin);
                }
                 //如果不是當前進程,那么代表進程間通訊彼硫,則返回代理Binder對象
                return new com.madnessxiong.server.IServer.Stub.Proxy(obj);
            }
    
  3. 獲得代理對象后炊豪,那么開始通信:

    clientConnection.getiServer().get();
    

    可以看到調(diào)用了代理對象的get(),看一下代碼:

                @Override
                public void get() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_get, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
    

    在這里首先對數(shù)據(jù)進行了序列化,然后調(diào)用了transact(),這是一個本地方法拧篮,最終會調(diào)用到talkWithDriver方法词渤,看名字就知道通訊過程交給了驅(qū)動完成,這個方法最后通過ioctl系統(tǒng)調(diào)用串绩,Client進程進入內(nèi)核態(tài)缺虐,Client調(diào)用get()的線程掛起等待返回。驅(qū)動然后去喚醒Server進程礁凡,調(diào)用Server本地Binder對象的onTransact()(實際上由Server端線程池完成),那么再看本地Binder對象的onTransact():

            @Override
            public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
                java.lang.String descriptor = DESCRIPTOR;
                switch (code) {
                    case INTERFACE_TRANSACTION: {
                        reply.writeString(descriptor);
                        return true;
                    }
                    case TRANSACTION_get: {
                        data.enforceInterface(descriptor);
                        this.get();
                        reply.writeNoException();
                        return true;
                    }
                    default: {
                        return super.onTransact(code, data, reply, flags);
                    }
                }
            }
    

    在這里根據(jù)調(diào)用號(每個AIDL方法都有一個編號高氮,在跨進程的時候,不會傳遞方法顷牌,而是傳遞編號指明調(diào)用哪個方法)剪芍,這里將調(diào)用本地的get(),然后返回給驅(qū)動韧掩。驅(qū)動喚醒掛起的Client線程紊浩,將結果返回。這樣就完成了一次進程間通訊疗锐。

總結:

  • 角色:以上就是AIDL的跨進程通訊過程坊谁,可以看到和Binder分析里的基本一致,Client及代理Binder對象滑臊,Server及本地Binder對象口芍,Stub繼承Binder接口進行進程間通訊功能。

  • 過程:代理Binder對象通過Binder驅(qū)動調(diào)用方法雇卷,Binder驅(qū)動通知本地Binder調(diào)用方法返回結果鬓椭,Binder驅(qū)動將結果返回給代理Binder,這樣就完成了一次進程間通訊关划。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末小染,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贮折,更是在濱河造成了極大的恐慌裤翩,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件调榄,死亡現(xiàn)場離奇詭異踊赠,居然都是意外死亡呵扛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門筐带,熙熙樓的掌柜王于貴愁眉苦臉地迎上來今穿,“玉大人,你說我怎么就攤上這事伦籍±渡梗” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵帖鸦,是天一觀的道長拔创。 經(jīng)常有香客問我,道長富蓄,這世上最難降的妖魔是什么剩燥? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮立倍,結果婚禮上灭红,老公的妹妹穿的比我還像新娘。我一直安慰自己口注,他們只是感情好变擒,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寝志,像睡著了一般娇斑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上材部,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天毫缆,我揣著相機與錄音,去河邊找鬼乐导。 笑死苦丁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的物臂。 我是一名探鬼主播旺拉,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼棵磷!你這毒婦竟也來了蛾狗?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤仪媒,失蹤者是張志新(化名)和其女友劉穎沉桌,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蒲牧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赌莺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冰抢。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖艘狭,靈堂內(nèi)的尸體忽然破棺而出挎扰,到底是詐尸還是另有隱情,我是刑警寧澤巢音,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布遵倦,位于F島的核電站,受9級特大地震影響官撼,放射性物質(zhì)發(fā)生泄漏梧躺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一傲绣、第九天 我趴在偏房一處隱蔽的房頂上張望掠哥。 院中可真熱鬧,春花似錦秃诵、人聲如沸续搀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禁舷。三九已至,卻和暖如春毅往,著一層夾襖步出監(jiān)牢的瞬間牵咙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工攀唯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留霜大,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓革答,卻偏偏與公主長得像战坤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子残拐,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

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