<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ByteBuffer 介紹及 C++ 實(shí)現(xiàn)

          共 17461字,需瀏覽 35分鐘

           ·

          2021-05-25 19:14

          • 1. ByteBuffer 介紹

          • 2. ByteBuffer 的成員變量

            • 2.1 幾個(gè)位置變量

            • 2.2 緩存區(qū)

            • 2.3 ByteBuffer 名稱

          • 3. 創(chuàng)建 ByteBuffer

            • 3.1 創(chuàng)建指定大小的空的 ByteBuffer

            • 3.2 從一個(gè)數(shù)組創(chuàng)建指定大小的 ByteBuffer

            • 3.3 析構(gòu)方法

          • 4. 狀態(tài)相關(guān)

            • 4.1 初始狀態(tài)

            • 4.2 寫入狀態(tài)

            • 4.3 讀取狀態(tài)

            • 4.4 mark() && discardMark()

            • 4.5 reset()

            • 4.6 rewind()

            • 4.7 compact()

            • 4.8 狀態(tài)相關(guān)方法總結(jié)

          • 5. put 數(shù)據(jù)

            • 5.1 擴(kuò)容機(jī)制

            • 5.2 模板方法

            • 5.3 put 方法

          • 6. get 數(shù)據(jù)

            • 6.1 模板方法

            • 6.2 get 方法

          • 7. 其他方法

            • 7.1 equals

            • 7.2 duplicate

            • 7.3 hasRemaining

            • 7.4 remaining

            • 7.5 printInfo

          • 8. ByteBuffer 的缺點(diǎn)

          ByteBuffer 介紹及 C++ 實(shí)現(xiàn)

          之前的工作中遇到過需要打包數(shù)據(jù)然后通過 USB 發(fā)送的功能,當(dāng)時(shí)寫了一個(gè)簡(jiǎn)單的類用來存入各種類型的數(shù)據(jù),然后將其 Buffer 內(nèi)的數(shù)據(jù)發(fā)送,接收到數(shù)據(jù)后通過它的方法再取出各種類型的數(shù)據(jù)。后來接觸到了 Java 的 ByteBuffer,發(fā)現(xiàn)兩者功能大致相同。本文會(huì)用 C++ 實(shí)現(xiàn) ByteBuffer 的大部分功能。

          1. ByteBuffer 介紹

          ByteBuffer類位于java.nio包下,它是一個(gè)字節(jié)緩存區(qū),提供了一些 put 和 get 方法,可以方便的將一些數(shù)據(jù)放到緩存區(qū)或者從緩存區(qū)里讀取某種類型的數(shù)據(jù)。ByteBuffer 的底層存儲(chǔ)結(jié)構(gòu)是數(shù)組,所有的操作都是基于該數(shù)組的操作。

          以下內(nèi)容結(jié)合 Java 版本 ByteBuffer 的原理以及 C++ 實(shí)現(xiàn)進(jìn)行講解。

          2. ByteBuffer 的成員變量

          2.1 幾個(gè)位置變量

          變量名稱含義
          position表示從寫入或者讀取的位置。
          limit處于寫入狀態(tài)時(shí),limit 和 capacity 相等;處于讀取狀態(tài)時(shí),表示數(shù)據(jù)索引的上限,也就是實(shí)際存放了多少數(shù)據(jù)。
          mark標(biāo)記讀取數(shù)據(jù)的起始位置,便于后續(xù)回退到該位置。
          capacity表示 ByteBuffer 的容量,也就是可以存放的最大字節(jié)數(shù)。

          這四個(gè)變量之間的關(guān)系可以表示為:mark <= position <= limit <= capacity

          數(shù)據(jù)的存入和讀取只會(huì)影響 position,不會(huì)影響 limit。

          在 C++ 實(shí)現(xiàn)中,設(shè)置如下成員變量:

          int32_t  mark_;
          uint32_t limit_;
          uint32_t position_;
          uint32_t capacity_;

          提供如下三個(gè)方法分別獲取 capacitypositionlimit

          uint32_t capacity() const;
          uint32_t position() const;
          uint32_t limit() const;

          提供如下兩個(gè)方法可以重新設(shè)置 limitposition

          ByteBuffer& limit(uint32_t newLimit);
          ByteBuffer& position(uint32_t newPosition);

          2.2 緩存區(qū)

          前面已經(jīng)提到,ByteBuffer 提供一個(gè)緩存區(qū)來存儲(chǔ)數(shù)據(jù),在 C++ 實(shí)現(xiàn)中,使用一個(gè) uint8_t 類型的數(shù)組進(jìn)行數(shù)據(jù)的存儲(chǔ)。在 ByteBuffer 類創(chuàng)建時(shí)申請(qǐng)空間,在 ByteBuffer 類銷毀時(shí)釋放空間。

          uint8_t*         p_buffer_;

          2.3 ByteBuffer 名稱

          為了打印時(shí)的美觀,為每一個(gè) ByteBuffer 設(shè)置一個(gè)名稱,該名稱為 ByteBuffer 類的一個(gè)成員變量,在類創(chuàng)建時(shí)設(shè)置,默認(rèn)為空:

          std::string      name_;

          3. 創(chuàng)建 ByteBuffer

          java.nio.Buffer 類是一個(gè)抽象類,不能被實(shí)例化。Buffer類的直接子類,如ByteBuffer等也是抽象類,所以也不能被實(shí)例化。但是 ByteBuffer 類提供了4個(gè)靜態(tài)工廠方法來獲得 ByteBuffer 的實(shí)例:

          • allocate(int capacity)
          • allocateDirect(int capacity)
          • wrap(byte[] array)
          • wrap(byte[] array, int offset, int length)

          C++ 版本做了一下簡(jiǎn)化,提供兩個(gè)構(gòu)造方法進(jìn)行創(chuàng)建。

          3.1 創(chuàng)建指定大小的空的 ByteBuffer

          // Default size of the buffer
          #define DEFAULT_BUFFER_SIZE 2048

          ByteBuffer(uint32_t capacity = DEFAULT_BUFFER_SIZE, const char* name = ""
              : mark_(-1), 
              limit_(capacity), 
              position_(0),
              capacity_(capacity), 
              name_(name)
          {
              p_buffer_ = NULL;
              p_buffer_ = (uint8_t*)calloc(capacity_, sizeof(uint8_t));
          }

          如果創(chuàng)建時(shí)未指定 capacity ,默認(rèn)大小為 2048 字節(jié)。

          3.2 從一個(gè)數(shù)組創(chuàng)建指定大小的 ByteBuffer

          ByteBuffer(uint8_t* arr, uint32_t length, const char* name = "")
              : mark_(-1), 
              limit_(length), 
              position_(0),
              capacity_(length), 
              name_(name)
          {
              p_buffer_ = NULL;
              p_buffer_ = (uint8_t*)calloc(capacity_, sizeof(uint8_t));

              putBytes(arr, capacity_);
              clear();
          }

          putBytes() 方法負(fù)責(zé)將一個(gè)現(xiàn)有數(shù)組的指定長(zhǎng)度存到 ByteBuffer 中,后面會(huì)對(duì)該方法做介紹。

          3.3 析構(gòu)方法

          析構(gòu)方法主要作用是釋放已經(jīng)申請(qǐng)的內(nèi)存:

          ~ByteBuffer()
          {
              if (p_buffer_)
              {
                  free(p_buffer_);
                  p_buffer_ = NULL;
              }
          }

          4. 狀態(tài)相關(guān)

          申請(qǐng)一個(gè)容量為 10 的 ByteBuffer bf,以下演示都基于 bf

          4.1 初始狀態(tài)

          image-20210323235205921

          初始狀態(tài)下,四個(gè)變量的值分別為:

          • mark:-1
          • position:0
          • limit:10
          • capacity:10

          將 ByteBuffer 置為初始狀態(tài)的方法:

          • ByteBuffer 創(chuàng)建之后調(diào)用其它方法之前就是初始狀態(tài)
          • 調(diào)用 clear() 方法可以重置到初始狀態(tài)。

          clear() 方法的 C++ 實(shí)現(xiàn)如下:

          ByteBuffer& clear() 
          {
              position_ = 0;
              mark_     = -1;
              limit_    = capacity_;
              return *this;
          }

          4.2 寫入狀態(tài)

          假設(shè)向 bf 寫入 hello 幾個(gè)字符,此時(shí)四個(gè)變量的狀態(tài)如圖所示:

          image-20210323235228538

          position 會(huì)隨著數(shù)據(jù)的寫入而后移。

          將 ByteBuffer 置為寫入狀態(tài)的方法:

          • ByteBuffer 創(chuàng)建之后就是寫入狀態(tài),可以調(diào)用一系列 put 方法寫入數(shù)據(jù);

          4.3 讀取狀態(tài)

          bf 進(jìn)入讀取狀態(tài)時(shí)四個(gè)變量的狀態(tài)如圖所示:

          image-20210324000012817

          調(diào)用一系列 get 方法從 bf 中讀取數(shù)據(jù),position 隨著數(shù)據(jù)的讀取會(huì)向后移動(dòng),但不會(huì)超過 limit

          bf 從寫入狀態(tài)進(jìn)入讀取狀態(tài)需要調(diào)用 flip() 方法,調(diào)用 flip() 方法后,limit 被設(shè)置為原 position 的值,表示已經(jīng)存儲(chǔ)數(shù)據(jù)的位置;position 被置為 0。

          flip() 方法的 C++ 實(shí)現(xiàn)如下:

          ByteBuffer& flip() 
          {
              limit_    = position_;
              position_ = 0;
              mark_     = -1;
              return *this;
          }

          4.4 mark() && discardMark()

          這兩個(gè)方法比較簡(jiǎn)單,mark() 方法將 mark 值設(shè)置為當(dāng)前的 positiondiscardMark() 方法將 mark 重置為 -1。調(diào)用 mark()discardMark() 方法后 mark 位置的變化如圖所示:

          這兩個(gè)方法的 C++ 代碼實(shí)現(xiàn)如下:

          ByteBuffer& mark()
          {
              mark_ = position_;
              return *this;
          }

          ByteBuffer& discardMark() 
          {
              mark_ = -1;
              return *this;
          }

          4.5 reset()

          reset() 方法將 position 恢復(fù)到 mark 的位置。調(diào)用 reset() 方法后的 position 變化如圖所示:

          image-20210324140402132

          reset() 方法的 C++ 實(shí)現(xiàn)如下:

          ByteBuffer& reset()
          {
              if (mark_ >= 0)
                  position_ = mark_;

              return *this;
          }

          4.6 rewind()

          rewind() 方法負(fù)責(zé)將 position 置為 0,將 mark 置為 -1,數(shù)據(jù)的內(nèi)容不會(huì)改變,一般在把數(shù)據(jù)重寫入Buffer前調(diào)用。調(diào)用 rewind() 方法后 markposition 的變化如圖所示:

          image-20210324140832110

          rewind() 方法的 C++ 實(shí)現(xiàn)如下:

          ByteBuffer& rewind()
          {
              mark_ = -1;
              position_ = 0;

              return *this;
          }

          4.7 compact()

          壓縮緩存區(qū)。把緩存區(qū)當(dāng)前 positionlimit 之間的數(shù)據(jù)移動(dòng)到緩存區(qū)的開頭。也就是說,將索引 p=position() 處的字節(jié)復(fù)制到索引 0 處,將索引 p+1 處的字節(jié)復(fù)制到索引 1 處。以此類推,直到 limit - 1 處的字節(jié)復(fù)制到索引 n=limit-1-p 處。然后將緩存區(qū)的 position 設(shè)置為 n+1(也就是不能再讀取數(shù)據(jù)了,但是可以寫入數(shù)據(jù)),并將 limit 的值設(shè)置為  capacity

          調(diào)用 compact() 方法后,幾個(gè)變量的位置以及數(shù)據(jù)的變化如圖所示:

          image-20210324192748457

          compact() 方法的 C++ 實(shí)現(xiàn)如下:

          ByteBuffer& compact()
          {
              do 
              {
                  if (position_ >= limit_)
                  {
                      position_ = 0;
                      break;
                  }

                  for (uint32_t i = 0; i < limit_ - position_; i++)
                  {
                      p_buffer_[i] = p_buffer_[position_ + i];
                  }
                  position_ = limit_ - position_;
              } while (0);        

              limit_ = capacity_;
              return *this;
          }

          4.8 狀態(tài)相關(guān)方法總結(jié)

          函數(shù)名描述
          flip()把 limit 設(shè)置為當(dāng)前 position,把 position 置為 0
          clear()重置ByteBuffer的 position = 0; limit = capacity; mark = -1,數(shù)據(jù)內(nèi)容無變化
          reset()將position恢復(fù)到mark處的位置
          rewind()執(zhí)行后position = 0, mark = -1,數(shù)據(jù)內(nèi)容不變
          mark()將mark值設(shè)置為當(dāng)前的position
          discardMark()將mark的位置重置為-1
          compact()刪除已讀過的數(shù)據(jù),將position到limit之間的數(shù)據(jù)移動(dòng)到0和limit-position處,并將mark重置為-1,position放到數(shù)據(jù)結(jié)尾,總結(jié)一下,就是可以繼續(xù)寫數(shù)據(jù)了,但是不能讀數(shù)據(jù)

          5. put 數(shù)據(jù)

          ByteBuffer 提供一系列的 put 方法將各種類型的數(shù)據(jù)放到 buffer 中,具體的類型有 char、short、int、long、float、double、char 數(shù)組以及 Bytebuffer。

          5.1 擴(kuò)容機(jī)制

          Java 的 ByteBuffer 在創(chuàng)建時(shí)容量就固定了,如果存放的數(shù)據(jù)超出容量,會(huì)拋出異常。C++ 版本的 ByteBuffer 增加了擴(kuò)容機(jī)制。理論上,每次向 buffer 中寫入數(shù)據(jù)都要檢查空間是否足夠,如果空間不夠,則擴(kuò)大容量。

          ByteBuffer 定義成員變量 BUFFER_SIZE_INCREASE 表示擴(kuò)容的步長(zhǎng),即每次擴(kuò)大的容量都為 BUFFER_SIZE_INCREASE  的整數(shù)倍,其值為 2048

          const uint32_t BUFFER_SIZE_INCREASE = 2048;

          定義 checkSize() 方法檢查容量是否足夠,如果足夠,不做處理;如果不夠,則計(jì)算需要多少容量并擴(kuò)容:

          void ByteBuffer::checkSize(uint32_t index, uint32_t increase)
          {
              if (index + increase <= capacity_)
                      return;

              uint32_t newSize = capacity_ + (increase + BUFFER_SIZE_INCREASE - 1) /
                  BUFFER_SIZE_INCREASE * BUFFER_SIZE_INCREASE;
              uint8_t* pBuf = (uint8_t*)realloc(p_buffer_, newSize);
              if (!pBuf)
              {
                  std::cout << "relloc failed!" << std::endl;
                  exit(1);
              }

              p_buffer_ = pBuf;
              capacity_ = newSize;
          }

          void ByteBuffer::checkSize(uint32_t increase)
          {
              checkSize(position_, increase);
          }

          在每個(gè) put() 方法里首先調(diào)用 checkSize() 檢查容量,然后再放入數(shù)據(jù)。

          5.2 模板方法

          為了簡(jiǎn)化存放數(shù)據(jù)的過程,用一個(gè)模板方法去適配各種類型:

          template<typename T>
          void append(T data)
          {
              if (!p_buffer_)
                  return;

              uint32_t s = sizeof(data);
              checkSize(s);

              memcpy(&p_buffer_[position_], (uint8_t*)&data, s);
              position_ += s;
          }

          template<typename T>
          void insert(T data, uint32_t index)
          {
              uint32_t s = sizeof(data);
              checkSize(index, s);

              position_ = index;
              append<T>(data);
          }

          append() 方法將數(shù)據(jù)寫入到當(dāng)前的 position_ 處,并相應(yīng)增加 position_

          insert() 方法將數(shù)據(jù)寫入到指定的位置,首先要將 position_ 設(shè)置為 index 然后調(diào)用 append() 方法寫入數(shù)據(jù)。

          5.3 put 方法

          ByteBuffer 提供的所有 put 方法返回值類型都為 ByteBuffer& 便于鏈?zhǔn)讲僮鳎热?bf.put(1).put("hello", 5).put(3.1415926),所有方法如下所示:

          ByteBuffer& put(ByteBuffer* bb);
          ByteBuffer& put(uint8_t value);
          ByteBuffer& put(uint8_t value, uint32_t index);
          ByteBuffer& putBytes(const uint8_t* buf, uint32_t len);
          ByteBuffer& putBytes(const uint8_t* buf, uint32_t len, uint32_t index);
          ByteBuffer& putChar(char value);
          ByteBuffer& putChar(char value, uint32_t index);
          ByteBuffer& putShort(uint16_t value);
          ByteBuffer& putShort(uint16_t value, uint32_t index);
          ByteBuffer& putInt(uint32_t value);
          ByteBuffer& putInt(uint32_t value, uint32_t index);
          ByteBuffer& putLong(uint64_t value);
          ByteBuffer& putLong(uint64_t value, uint32_t index);
          ByteBuffer& putFloat(float value);
          ByteBuffer& putFloat(float value, uint32_t index);
          ByteBuffer& putDouble(double value);
          ByteBuffer& putDouble(double value, uint32_t index);

          注意:由于 Java 采用 Unicode 編碼,一個(gè) Char 類型占兩個(gè)字節(jié),但是在 C++ 中 char 類型占一個(gè)字節(jié),所以兩個(gè)版本的 putChar() 方法有些差異。

          著重講解一下 ByteBuffer& put(ByteBuffer* bb) 方法,該方法將另一個(gè) ByteBuffer 的內(nèi)容(從 0limit() 之間的數(shù)據(jù))拷貝到當(dāng)前 ByteBuffer,其實(shí)現(xiàn)為:

          ByteBuffer& put(ByteBuffer* bb)
          {
              for (uint32_t i = 0; i < bb->limit(); i++)
                  append<uint8_t>(bb->get(i));

              return *this;
          }

          6. get 數(shù)據(jù)

          ByteBuffer 提供一系列的 get 方法將各種類型的數(shù)據(jù)放到 buffer 中。

          6.1 模板方法

          為了簡(jiǎn)化數(shù)據(jù)的獲取,實(shí)現(xiàn)模板方法獲取各種類型的數(shù)據(jù)。注意:帶有 index 參數(shù)的 read() 方法不會(huì)改變 position 的值。

          template <typename T>
          read(uint32_t index) const
          {
              if (!p_buffer_ || index + sizeof(T) > limit_)
                  return 0;

              return *((T*)&p_buffer_[index]);
          }

          template <typename T>
          read()
          {
              T data = read<T>(position_);
              position_ += sizeof(T);
              return data;
          }

          6.2 get 方法

          所有 get() 方法如下:

          uint8_t  get();
          uint8_t  get(uint32_t index) const;
          void     getBytes(uint8_t* buf, uint32_t len);
          void     getBytes(uint32_t index, uint8_t* buf, uint32_t len) const;
          char     getChar();
          char     getChar(uint32_t index) const;
          uint16_t getShort();
          uint16_t getShort(uint32_t index) const;
          uint32_t getInt();
          uint32_t getInt(uint32_t index) const;
          uint64_t getLong();
          uint64_t getLong(uint32_t index) const;
          float    getFloat();
          float    getFloat(uint32_t index) const;
          double   getDouble();
          double   getDouble(uint32_t index) const;

          注意:帶有 index 參數(shù)的方法不會(huì)改變 position_ 值。

          看一下 void getBytes(uint32_t index, uint8_t* buf, uint32_t len) const 方法的實(shí)現(xiàn):

          void ByteBuffer::getBytes(uint32_t index, uint8_t* buf, uint32_t len) const
          {
              // 合法性檢測(cè)
              if (!p_buffer_ || index + len > limit_)
                  return;

              uint32_t pos = index;
              for (uint32_t i = 0; i < len; i++)
              {
                  buf[i] = p_buffer_[pos++];
              }
          }

          為了實(shí)現(xiàn)只做一次合法性檢測(cè),并沒有調(diào)用 read() 模板方法。

          7. 其他方法

          7.1 equals

          函數(shù)原型:bool equals(ByteBuffer* other);

          描述:比較兩個(gè) ByteBuffer 是否相等;

          實(shí)現(xiàn):

          bool equals(ByteBuffer* other)
          {
              uint32_t len = limit();
              if (len != other->limit())
                  return false;

              for (uint32_t i = 0; i < len; i++)
              {
                  if (get(i) != other->get(i))
                      return false;
              }
              return true;
          }

          7.2 duplicate

          函數(shù)原型:ByteBuffer* duplicate();

          描述:復(fù)制一個(gè) ByteBuffer;

          實(shí)現(xiàn):新的 ByteBuffer 的 mark-1,不一樣和原 ByteBuffer 相同。

          ByteBuffer* ByteBuffer::duplicate()
          {
              ByteBuffer* newBuffer = new ByteBuffer(capacity_);

              // copy data
              newBuffer->put(this);

              newBuffer->limit(limit_);
              newBuffer->position(position_);

              return newBuffer;
          }

          7.3 hasRemaining

          函數(shù)原型:bool hasRemaining()

          描述:表示 positionlimit 之間是否還有數(shù)據(jù);

          實(shí)現(xiàn):

          bool hasRemaining()
          {
              return limit_ > position_;
          }

          7.4 remaining

          函數(shù)原型:uint32_t remaining() const

          描述:返回 positionlimit 之間字節(jié)數(shù);

          實(shí)現(xiàn):

          uint32_t remaining() const
          {
              return position_ < limit_ ? limit_ - position_ : 0;
          }

          7.5 printInfo

          函數(shù)原型:void printInfo() const

          描述:打印 markpositionlimitcapacity 的值

          實(shí)現(xiàn):

          void printInfo() const
          {
              std::cout << "ByteBuffer " << name_ << ":\n"
                  << "\tmark(" << mark_ << "), "
                  << "position(" << position_ << "), "
                  << "limit(" << limit_ << "), "
                  << "capacity(" << capacity_ << ")." << std::endl;
          }

          8. ByteBuffer 的缺點(diǎn)

          ByteBuffer 缺點(diǎn)如下:

          • ByteBuffer 并不是線程安全的,如果想要在并發(fā)情況下使用,需要自己為緩存區(qū)做同步控制;

          • ByteBuffer 長(zhǎng)度固定,一旦分配完成,它的容量不能動(dòng)態(tài)擴(kuò)展和收縮,當(dāng)需要編碼的對(duì)象大于 ByteBuffer 的容量時(shí),會(huì)發(fā)生索引越界異常;

          • ByteBuffer 只有一個(gè)標(biāo)識(shí)位的指針 position,讀寫的時(shí)候需要手工調(diào)用 flip()rewind()等,使用者必須小心謹(jǐn)慎地處理這些 API,否則很容易導(dǎo)致程序處理失敗;

          • ByteBuffer 的 API 功能有限,一些高級(jí)和實(shí)用的特性它不支持,需要使用者自己編程實(shí)現(xiàn)。

          本文的 C++ 實(shí)現(xiàn)只對(duì)第二點(diǎn)做了調(diào)整,支持了主動(dòng)擴(kuò)容;同樣存在其它幾個(gè)缺點(diǎn)。

          ByteBuf 是 Netty 里的封裝的數(shù)據(jù)緩存區(qū),區(qū)別于 ByteBuffer 里需要 positionlimitcapacity 等屬性來操作 ByteBuffer 數(shù)據(jù)讀寫,而 ByteBuf 是通過兩個(gè)指針協(xié)助完成緩存區(qū)的讀寫操作,后續(xù)可能實(shí)現(xiàn) C++ 版本的 ByteBuf 或者對(duì)當(dāng)前 C++ ByteBuffer 進(jìn)行修改。

          參考鏈接

          [1]

          Class ByteBuffer: https://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html

          [2]

          java.nio.Buffer 中的 flip()方法: https://blog.csdn.net/hbtj_1216/article/details/53129588

          [3]

          ByteBuffer常用方法詳解: https://blog.csdn.net/moakun/article/details/80630477

          [4]

          ByteBuffer詳解: http://bcoder.com/java/explaination-of-bytebuffer

          [5]

          圖解ByteBuffer和ByteBuf: https://www.gameboys.cn/article/193

          點(diǎn)分享

          點(diǎn)收藏

          點(diǎn)點(diǎn)贊

          點(diǎn)在看

          瀏覽 120
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  吖v在线视频免费观看免费观看 | 乐播一区二区三区 | 色无码毛片 | 翔田千里av | 美国发布站久久 |