在网络编程中,数据的读写是非常常见的操作。由于网络数据的收发往往是异步的,而且数据的大小和到达时间都是不确定的,因此需要一个缓冲区来暂存这些数据。Buffer
类就是为了解决这个问题而设计的,它提供了一个灵活的缓冲区管理机制,能够方便地处理数据的读写操作。在 muduo 网络库中,Buffer
类扮演着重要的角色,下面我们将详细讲解其功能和实现细节。
Buffer
类主要提供了以下几个核心功能:
std::vector
来存储数据,方便动态扩容。readerIndex
和 writerIndex
来管理可读数据和可写数据的范围。private:
std::vector buffer_;
size_t readerIndex_;
size_t writerIndex_;
buffer_
:使用 std::vector
作为底层存储容器,它可以动态地调整大小,方便存储不同长度的数据。readerIndex_
:表示可读数据的起始位置,即从 buffer_
中 readerIndex_
位置开始的数据是可以读取的。writerIndex_
:表示可写数据的起始位置,即可以从 buffer_
中 writerIndex_
位置开始写入数据。public:
static const size_t kCheapPrepend = 8;
static const size_t kInitialSize = 1024;
kCheapPrepend
:预留的前置空间,用于在数据前面添加一些额外的信息,例如数据长度等。kInitialSize
:缓冲区的初始大小。explicit Buffer(size_t initalSize = kInitialSize)
: buffer_(kCheapPrepend + initalSize)
, readerIndex_(kCheapPrepend)
, writerIndex_(kCheapPrepend)
{
}
构造函数初始化了 buffer_
的大小为 kCheapPrepend + initalSize
,并将 readerIndex_
和 writerIndex_
都初始化为 kCheapPrepend
,这意味着在 buffer_
的前 kCheapPrepend
个字节是预留的前置空间。
size_t readableBytes() const { return writerIndex_ - readerIndex_; }
size_t writableBytes() const { return buffer_.size() - writerIndex_; }
size_t prependableBytes() const { return readerIndex_; }
readableBytes()
:返回当前缓冲区中可读的字节数,即 writerIndex_
与 readerIndex_
之间的差值。writableBytes()
:返回当前缓冲区中可写的字节数,即 buffer_
的总大小减去 writerIndex_
。prependableBytes()
:返回前置空间的字节数,即 readerIndex_
。const char *peek() const { return begin() + readerIndex_; }
peek()
函数返回一个指向可读数据起始位置的指针,方便读取数据。
char *beginWrite() { return begin() + writerIndex_; }
const char *beginWrite() const { return begin() + writerIndex_; }
beginWrite()
函数返回一个指向可写数据起始位置的指针,方便写入数据。
void retrieve(size_t len)
{
if (len < readableBytes())
{
readerIndex_ += len;
}
else
{
retrieveAll();
}
}
void retrieveAll()
{
readerIndex_ = kCheapPrepend;
writerIndex_ = kCheapPrepend;
}
retrieve(size_t len)
:当读取的数据长度小于可读数据长度时,将 readerIndex_
向后移动 len
个字节;否则,调用 retrieveAll()
函数将 readerIndex_
和 writerIndex_
都重置为 kCheapPrepend
。retrieveAll()
:将 readerIndex_
和 writerIndex_
都重置为 kCheapPrepend
,表示缓冲区中的数据已经全部读取完毕。void append(const char *data, size_t len)
{
ensureWritableBytes(len);
std::copy(data, data+len, beginWrite());
writerIndex_ += len;
}
append(const char *data, size_t len)
:在写入数据之前,先调用 ensureWritableBytes(len)
函数确保缓冲区有足够的空间;然后将数据复制到可写位置;最后将 writerIndex_
向后移动 len
个字节。
void ensureWritableBytes(size_t len)
{
if (writableBytes() < len)
{
makeSpace(len); // 扩容
}
}
void makeSpace(size_t len)
{
if (writableBytes() + prependableBytes() < len + kCheapPrepend)
{
buffer_.resize(writerIndex_ + len);
}
else
{
size_t readable = readableBytes();
std::copy(begin() + readerIndex_,
begin() + writerIndex_,
begin() + kCheapPrepend);
readerIndex_ = kCheapPrepend;
writerIndex_ = readerIndex_ + readable;
}
}
ensureWritableBytes(size_t len)
:检查缓冲区是否有足够的可写空间,如果没有,则调用 makeSpace(len)
函数进行扩容。makeSpace(size_t len)
:当可写空间和前置空间之和小于 len + kCheapPrepend
时,直接将 buffer_
的大小调整为 writerIndex_ + len
;否则,将可读数据移动到前置空间之后,重新调整 readerIndex_
和 writerIndex_
的位置。在Buffer::readFd
函数中,使用了readv
系统调用实现集中读(也称为 “分散 - 聚集读”),其核心思想是从一个数据源(如文件描述符)读取数据并分散存储到多个缓冲区中。这一机制在网络编程中非常实用,尤其当缓冲区空间不足时,可通过额外的临时缓冲区避免数据丢失。
ssize_t Buffer::readFd(int fd, int *saveErrno)
{
char extrabuf[65536] = {0};
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);
const int iovcnt = (writable < sizeof(extrabuf)) ? 2 : 1;
const ssize_t n = ::readv(fd, vec, iovcnt);
if (n < 0)
{
*saveErrno = errno;
}
else if (n <= writable)
{
writerIndex_ += n;
}
else
{
writerIndex_ = buffer_.size();
append(extrabuf, n - writable);
}
return n;
}
readFd(int fd, int *saveErrno)
:使用 readv
函数从文件描述符 fd
读取数据,readv
函数可以将数据分散到多个缓冲区中。如果读取的数据长度小于等于可写空间,则直接更新 writerIndex_
;否则,将剩余的数据追加到缓冲区中。
集中读的核心优势:
- 减少系统调用次数:一次
readv
可替代多次read
,降低内核态与用户态切换开销。- 灵活处理缓冲区空间:当 Buffer 自身可写空间不足时,通过临时缓冲区
extrabuf
扩展,避免因空间不足导致数据读取不完整。- 零拷贝潜力:数据直接读取到多个目标缓冲区,减少内存拷贝次数。
ssize_t Buffer::writeFd(int fd, int *saveErrno)
{
ssize_t n = ::write(fd, peek(), readableBytes());
if (n < 0)
{
*saveErrno = errno;
}
return n;
}
writeFd(int fd, int *saveErrno)
:使用 write
函数将缓冲区中的可读数据写入到文件描述符 fd
中。
Buffer
类是 muduo 网络库中一个非常重要的组件,它提供了一个灵活的缓冲区管理机制,能够方便地处理数据的读写操作。通过 readerIndex
和 writerIndex
的移动设计,实现了对可读数据和可写数据的有效管理。同时,通过扩容机制,保证了缓冲区能够动态地适应不同大小的数据。在实际应用中,需要考虑线程安全和性能优化等问题,以提高系统的稳定性和性能。