剖析Muduo Buffer
本文部分内容摘自陈硕的Blog – Muduo设计与实现之一:Buffer类的设计,并附上自己的理解,如有不对,请指出🤩
Buffer的要求
Muduo Buffer的设计要点:
- 对外表现为一块连续的内存(char*, len)
- 是一个动态数组,大小可以自动增长适应不同大小的消息
- 内部以
vector of char
来保存数据,并提供相应的接口
按照陈硕的话来讲,Muduo Buffer 就像一个queue
,从末尾写入数据,从头部读出数据。
Buffer的数据结构
Buffer 的内部是一个 vector of char,它是一块连续的内存。此外,Buffer 有两个 data
members,指向该 vector 中的元素。这两个 indices 的类型是 int,不是 char*,目的是应对
迭代器失效。muduo Buffer 的设计参考了 Netty 的 ChannelBuffer 和 libevent 1.4.x 的
evbuffer。
Muduo Buffer 的数据结构如下:
两个 indices 把 vector 的内容分为三块:prependable、readable(可读区域)、writable(可写区域),各块的大小是(公式一):
prependable = readIndex
readable = writeIndex - readIndex
writable = size() - writeIndex
readIndex 和 writeIndex 满足以下不变式(invariant):
0 ≤ readIndex ≤ writeIndex ≤ data.size()
Muduo Buffer 里有两个常数 kCheapPrepend 和 kInitialSize,定义了 prependable 的初始大小和 writable 的初始大小。(readable 的初始大小为 0。)在初始化之后,Buffer 的数据结构如下:括号里的数字是该变量或常量的值。
根据以上(公式一)可算出各块的大小,刚刚初始化的 Buffer 里没有 payload 数据,所以 readable == 0。
以下是muduo::Buffer的类图,图片来源于陈硕的博客
具体的操作可以去看以下陈硕的博客,写的很清楚。
Buffer::readFd()
陈硕原话:
- 在非阻塞网络编程中,如何设计并使用缓冲区?一方面我们希望减少系统调用,一次读的数据越多越划算,那么似乎应该准备一个大的缓冲区。另一方面,我们系统减少内存占用。如果有 10k 个连接,每个连接一建立就分配 64k 的读缓冲的话,将占用 640M 内存,而大多数时候这些缓冲区的使用率很低。muduo 用 readv 结合栈上空间巧妙地解决了这个问题。
系统调用readv在UNIX 网络编程306页有讲解,具体作用为:运行一个系统调用读取多个缓冲区。并把这种操作称为分散读。
在muduo中如何运用readv?
- 在栈上准备65536字节的
extrabuf
- 比较Buffer可写字节数
writable
和extrabuf
的大小 - 若Buffer可写字节数
writable
小于extrabuf
,那么用这两块缓冲区作为分散读的操作对象 - 反之,只用
writable
作为分散读的操作对象(其实就是若writable
>65535字节,就选择一个缓冲区;writable
<= 65535字节,就选择他们两个缓冲区) - 这样如果读入的数据不多,那么全部都读到 Buffer 中去了;如果长度超过 Buffer 的 writable 字节数,就会读到栈上的 extrabuf里,然后程序再把 extrabuf里的数据 append 到 Buffer 中。
这么做的好处:
- 利用栈上的空间做临时空间,避免开辟巨大的 Buffer 造成浪费
- 避免反复调用 read 系统调用
大致代码如下:
ssize_t Buffer::readFd(int fd, int *saveErrno)
{ char extrabuf[65536] = {0};struct iovec vec[2]; const std::size_t writable = writableBytes();// Buffer底层缓冲区可写的空间大小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){*savenoErrno = errno;}else if(n <= writable){writerIndex_ += n;}else{writerIndex_ = buffer_.size();append(extrabuf, n - writable);}return n;
}writerIndex_ = buffer_.size();append(extrabuf, n - writable);}return n;
}