muduo源码剖析 - Buffer

说明

   TcpConnection有两个缓冲区,一个输入缓冲区,一个输出缓冲区。

   提出问题:为什么non-blocking网络编程中应用层buffer是必须的

outputBuffer必须有

场景:程序想通过tcp连接发送100k的数据,但是write调用时操作系统只接受了80k(受内核发送缓冲区影响),你肯定不想原地等待。应该快速交出线程控制权。返回eventloop。
   剩下的20k数据,由网络库接管(TcpConnection),注册POLLOUT事件(写事件)并在可写事件发生时发送剩下数据。发送完毕停止关注POLLOUT事件。以免造成busy loop。

 inputBuffer必须有

tcp是一个无边界的字节流协议。接收方必须处理两种情况。1. 收到的数据尚不构成一条完整的信息       2.一次收到两条消息的数据。  网络库处理socket可读事件,必须一次性的把内核buffer读到应用层buffer,否则会反复触发POLLIN事件(LT模式)造成busy-loop,那么必然出现“数据不完整的情况",所以先放到inputBuffer中等构成一条完整的消息再通知程序的业务逻辑(这通常是codec的职责)

  其次,必须先理解缓冲区的结构,其他操作都是基于结构的。只有先理解结构,其他问题迎刃而解。陈硕已经在注释中写的很清楚了。

/// +-------------------+------------------+------------------+
/// | prependable bytes |  readable bytes  |  writable bytes  |
/// |                   |     (CONTENT)    |                  |
/// +-------------------+------------------+------------------+
/// |                   |                  |                  |
/// 0      <=      readerIndex   <=   writerIndex    <=     size

三个区域被两个index切分:

1. prependable区域:留白方便数据插入后前叉长度等数据使用。

2. readable区域:可读区域,处于readerIndex和writerIndex之间。

3. writable区域:可写区域,写数据和追加数据从writerIndex开始。

提出问题:为什么使用index而不使用迭代器呢?

因为缓冲区维护的是vector,  会自动扩容,扩容后迭代器失效。用index更能方便表示可读可写的具体位置。

成员变量

重点就三个:

1. buffer:缓存容器

2. readerIndex:读位置下标

3. writerIndex: 写位置下标

  std::vector<char> buffer_;  //缓存容器 
  size_t readerIndex_;        //读位置下标
  size_t writerIndex_;        //写位置下标

  static const char kCRLF[];   //就是存储’\r\n’

方法

1. readFd: 最重要的一个函数。通过将缓冲区容器和栈上数组合成一个IO向量数组,使用readv往向量数组读取数据。小于当前缓冲区容量则改变writerIndex_即可,大于的话就把栈上的剩余数据append到缓冲区。提高了内存使用效率,如果一开始就直接分配64k空间的话,可能用不到,就浪费空间了

补充:readv读取数据到Io向量数组,会将数据按io向量容器次序依次放入数据。

// 从套接字读取数据然后添加到缓冲区中,使用栈上的空间,避免内存使用过大,并且一次尽可能读取更多的数据
// vec[2]为两块缓冲区,第一块指向写的位置,第二块指向栈上的缓冲区,如果第一块缓冲区足够容纳缓冲区数据,就直接返回了.否则才使用第二块缓冲区,即栈上的空间,
// 在append()到缓冲区,这样就提高了内存使用效率,如果一开始就直接分配64k空间的话,可能用不到,就浪费空间了.
// 这里使用了自己封装的readv,实际还是调用标准的readv(),这里的第三个参数是指明有多少块内存数据需要从fd读出.如果可写空间<64k的话,这里就填2,否则就填1.
// 这里这个判断不咋理解,不明白为什么这样判断
ssize_t Buffer::readFd(int fd, int* savedErrno)
{
  // saved an ioctl()/FIONREAD call to tell how much to read
  char extrabuf[65536];
  struct iovec vec[2];
  const size_t writable = writableBytes();
  vec[0].iov_base = begin()+writerIndex_;
  vec[0].iov_len = writable;
  vec[1].iov_base = extrabuf;
  vec[1].iov_len = sizeof extrabuf;
  // when there is enough space in this buffer, don't read into extrabuf.
  // when extrabuf is used, we read 128k-1 bytes at most.
  const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;
  const ssize_t n = sockets::readv(fd, vec, iovcnt);
  if (n < 0)
  {
    *savedErrno = errno;
  }
  else if (implicit_cast<size_t>(n) <= writable)
  {
    writerIndex_ += n;
  }
  else
  {
    writerIndex_ = buffer_.size();
    append(extrabuf, n - writable);
  }
  // if (n == writable + sizeof extrabuf)
  // {
  //   goto line_30;
  // }
  return n;
}

剩下的方法都是在基本结构上进行操作,看注释即可

  // implicit copy-ctor, move-ctor, dtor and assignment are fine
  // NOTE: implicit move-ctor is added in g++ 4.6
  //交换缓冲区
  void swap(Buffer& rhs)
  {
    buffer_.swap(rhs.buffer_);
    std::swap(readerIndex_, rhs.readerIndex_);
    std::swap(writerIndex_, rhs.writerIndex_);
  }

  //可读的数据大小,写位置-读位置
  size_t readableBytes() const
  { return writerIndex_ - readerIndex_; }
  
  //可写的空间大小,buffer_大小-写位置
  size_t writableBytes() const
  { return buffer_.size() - writerIndex_; }
  
  //返回readerIndex_,表示之前有多大的空间是空的
  size_t prependableBytes() const
  { return readerIndex_; }
 
  //返回读位置的指针
  const char* peek() const
  { return begin() + readerIndex_; }
  
  //这个就是在读位置和写位置之间的数据中找’\r\n’换行,找到的话返回这个指针,没找到返回NULL
  const char* findCRLF() const
  {
    // FIXME: replace with memmem()?
    const char* crlf = std::search(peek(), beginWrite(), kCRLF, kCRLF+2);
    return crlf == beginWrite() ? NULL : crlf;
  }

  const char* findCRLF(const char* start) const
  {
    assert(peek() <= start);
    assert(start <= beginWrite());
    // FIXME: replace with memmem()?
    const char* crlf = std::search(start, beginWrite(), kCRLF, kCRLF+2);
    return crlf == beginWrite() ? NULL : crlf;
  }
  //从start位置开始找,功能如上
  const char* findEOL() const
  {
    const void* eol = memchr(peek(), '\n', readableBytes());
    return static_cast<const char*>(eol);
  }

  const char* findEOL(const char* start) const
  {
    assert(peek() <= start);
    assert(start <= beginWrite());
    const void* eol = memchr(start, '\n', beginWrite() - start);
    return static_cast<const char*>(eol);
  }

  // retrieve returns void, to prevent
  // string str(retrieve(readableBytes()), readableBytes());
  // the evaluation of two functions are unspecified
// 就是取完数据后(在其他函数中实现,),这个只移动读指针
// 如果读指针和写指针重叠,也就是数据取完了,就都置为初始位置.
  void retrieve(size_t len)
  {
    assert(len <= readableBytes());
    if (len < readableBytes())
    {
      readerIndex_ += len;
    }
    else
    {
      retrieveAll();
    }
  } 

  //这个参数为指针,把这个指针之前的数据读取完,移动读指针
  void retrieveUntil(const char* end)
  {
    assert(peek() <= end);
    assert(end <= beginWrite());
    retrieve(end - peek());
  }
   // 下面面四个分别是取出8,4,2,1字节,retrieve*系列函数都比较简单
  void retrieveInt64()
  {
    retrieve(sizeof(int64_t));
  }

  void retrieveInt32()
  {
    retrieve(sizeof(int32_t));
  }

  void retrieveInt16()
  {
    retrieve(sizeof(int16_t));
  }

  void retrieveInt8()
  {
    retrieve(sizeof(int8_t));
  }

  //把读写下标置为初始位置
  void retrieveAll()
  {
    readerIndex_ = kCheapPrepend;
    writerIndex_ = kCheapPrepend;
  }
  //以下两个,是吧读取的数据转换成字符串返回,并调用retrieve移动读下标
  string retrieveAllAsString()
  {
    return retrieveAsString(readableBytes());
  }

  string retrieveAsString(size_t len)
  {
    assert(len <= readableBytes());
    string result(peek(), len);
    retrieve(len);
    return result;
  }
  //把可读数据转换为StringPiece类型返回
  StringPiece toStringPiece() const
  {
    return StringPiece(peek(), static_cast<int>(readableBytes()));
  }

  //以下三个就是把数据添加到buffer_缓冲区
  void append(const StringPiece& str)
  {
    append(str.data(), str.size());
  }

  void append(const char* /*restrict*/ data, size_t len)
  {
    ensureWritableBytes(len);
    std::copy(data, data+len, beginWrite());
    hasWritten(len);
  }

  void append(const void* /*restrict*/ data, size_t len)
  {
    append(static_cast<const char*>(data), len);
  }

  //就是确保缓冲区空间够写,动态增长空间
  void ensureWritableBytes(size_t len)
  {
    if (writableBytes() < len)
    {
      makeSpace(len);
    }
    assert(writableBytes() >= len);
  }
  //返回写位置的指针
  char* beginWrite()
  { return begin() + writerIndex_; }

  const char* beginWrite() const
  { return begin() + writerIndex_; }

  //移动写位置下标,+len
  void hasWritten(size_t len)
  {
    assert(len <= writableBytes());
    writerIndex_ += len;
  }

  //写位置下标-len
  void unwrite(size_t len)
  {
    assert(len <= readableBytes());
    writerIndex_ -= len;
  }

  ///
  /// Append int64_t using network endian
  ///
  //以下四个把主机字节序的转换成网络字节序追加到缓冲区后面
  void appendInt64(int64_t x)
  {
    int64_t be64 = sockets::hostToNetwork64(x);
    append(&be64, sizeof be64);
  }

  ///
  /// Append int32_t using network endian
  ///
  void appendInt32(int32_t x)
  {
    int32_t be32 = sockets::hostToNetwork32(x);
    append(&be32, sizeof be32);
  }

  void appendInt16(int16_t x)
  {
    int16_t be16 = sockets::hostToNetwork16(x);
    append(&be16, sizeof be16);
  }

  void appendInt8(int8_t x)
  {
    append(&x, sizeof x);
  }

  ///
  /// Read int64_t from network endian
  ///
  /// Require: buf->readableBytes() >= sizeof(int32_t)
  //以下4个读取这些类型的数据返回,并移动读下标,代码比较好理解
  int64_t readInt64()
  {
    int64_t result = peekInt64();
    retrieveInt64();
    return result;
  }

  ///
  /// Read int32_t from network endian
  ///
  /// Require: buf->readableBytes() >= sizeof(int32_t)
  int32_t readInt32()
  {
    int32_t result = peekInt32();
    retrieveInt32();
    return result;
  }

  int16_t readInt16()
  {
    int16_t result = peekInt16();
    retrieveInt16();
    return result;
  }

  int8_t readInt8()
  {
    int8_t result = peekInt8();
    retrieveInt8();
    return result;
  }

  ///
  /// Peek int64_t from network endian
  ///
  /// Require: buf->readableBytes() >= sizeof(int64_t)
  //以下4个把可读缓冲区中相应字节大小的数据读出来返回
  int64_t peekInt64() const
  {
    assert(readableBytes() >= sizeof(int64_t));
    int64_t be64 = 0;
    ::memcpy(&be64, peek(), sizeof be64);
    return sockets::networkToHost64(be64);
  }

  ///
  /// Peek int32_t from network endian
  ///
  /// Require: buf->readableBytes() >= sizeof(int32_t)
  int32_t peekInt32() const
  {
    assert(readableBytes() >= sizeof(int32_t));
    int32_t be32 = 0;
    ::memcpy(&be32, peek(), sizeof be32);
    return sockets::networkToHost32(be32);
  }

  int16_t peekInt16() const
  {
    assert(readableBytes() >= sizeof(int16_t));
    int16_t be16 = 0;
    ::memcpy(&be16, peek(), sizeof be16);
    return sockets::networkToHost16(be16);
  }

  int8_t peekInt8() const
  {
    assert(readableBytes() >= sizeof(int8_t));
    int8_t x = *peek();
    return x;
  }

  ///
  /// Prepend int64_t using network endian
  ///
  //以下5个在可读数据前面增加一些数据
  void prependInt64(int64_t x)
  {
    int64_t be64 = sockets::hostToNetwork64(x);
    prepend(&be64, sizeof be64);
  }

  ///
  /// Prepend int32_t using network endian
  ///
  void prependInt32(int32_t x)
  {
    int32_t be32 = sockets::hostToNetwork32(x);
    prepend(&be32, sizeof be32);
  }

  void prependInt16(int16_t x)
  {
    int16_t be16 = sockets::hostToNetwork16(x);
    prepend(&be16, sizeof be16);
  }

  void prependInt8(int8_t x)
  {
    prepend(&x, sizeof x);
  }

  void prepend(const void* /*restrict*/ data, size_t len)
  {
    assert(len <= prependableBytes());
    readerIndex_ -= len;
    const char* d = static_cast<const char*>(data);
    std::copy(d, d+len, begin()+readerIndex_);
  }
  
  //缩小空间,使得空间为可读数据+reserve的大小
  void shrink(size_t reserve)
  {
    // FIXME: use vector::shrink_to_fit() in C++ 11 if possible.
    Buffer other;
    other.ensureWritableBytes(readableBytes()+reserve);
    other.append(toStringPiece());
    swap(other);
  }
  //返回buffer_的capacity()
  size_t internalCapacity() const
  {
    return buffer_.capacity();
  }

  /// Read data directly into buffer.
  ///
  /// It may implement with readv(2)
  /// @return result of read(2), @c errno is saved
  ssize_t readFd(int fd, int* savedErrno);

 private:
  //以下两个返回buffer_.begin()
  char* begin()
  { return &*buffer_.begin(); }

  const char* begin() const
  { return &*buffer_.begin(); }
 
  //对空间的动态调整,如果读位置前面可用的空间+写位置后面可用的空间够用的话,把数据复制到开始,如果不够用的话,再在后面动态增加空间
  void makeSpace(size_t len)
  {
    if (writableBytes() + prependableBytes() < len + kCheapPrepend)
    {
      // FIXME: move readable data
      buffer_.resize(writerIndex_+len);
    }
    else
    {
      // move readable data to the front, make space inside buffer
      assert(kCheapPrepend < readerIndex_);
      size_t readable = readableBytes();
      std::copy(begin()+readerIndex_,
                begin()+writerIndex_,
                begin()+kCheapPrepend);
      readerIndex_ = kCheapPrepend;
      writerIndex_ = readerIndex_ + readable;
      assert(readable == readableBytes());
    }
  }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YanWenCheng_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值