Envoy 源码分析(二) ——–buffer
Envoy的buffer实现基于Libevent的evbuffer,在evbuffer的基础上做了一些简单的封装,如果对evbuffer不甚了解的话,可以查看下这个blog:https://blog.csdn.net/windeal3203/article/details/52864994
BufferFragmentImpl
这个类非常简单,保存了指向内存地址的指针,内存的长度和释放内存的函数对象,调用的接口done来释放内存。
const void* const data_;
const size_t size_;
const std::function<void(const void*, size_t, const BufferFragmentImpl*)> releasor_;
需要注意的是这个类是不可复制的,所有成员变量只有在构造函数时赋值之后不可更改。
OwnedImpl
这个类包含了一个evbuffer对象,并对相关的读写操作做了再次的封装:
Event::Libevent::BufferPtr buffer_;
BufferPtr 是在common里提到过的用unique_ptr封装的C类指针:
typedef CSmartPtr<evbuffer, evbuffer_free> BufferPtr;
struct RawSlice {
void* mem_ = nullptr;
size_t len_ = 0;
};
这里的RawSlice结构体和Libevent中的evbuffer_iovec结构体完全相同,并且和Linux的iovec也相同
struct evbuffer_iovec {
/** The start of the extent of memory. */
void *iov_base;
/** The length of the extent of memory. */
size_t iov_len;
};
首先看下三个add重载
void add(const void* data, uint64_t size) override;
void add(const std::string& data) override;
void add(const Instance& data) override;
这个三个函数都是通过evbuffer_add函数向evbuffer添加数据,这里会发生复制行为,所以add之后可以析构之前的存储。
void addBufferFragment(BufferFragment& fragment) override;
这个函数内部调用的evbuffer_add_reference,这里是通过引用向evbuffer添加数据,没有复制行为发生,需要传进回调函数在内存失效时析构内存。在使用期间要保证内存的有效生命周期。
这里着重看下read和write函数,实现了对socket的读写操作,用到的系统调用是:readv和writev 分块读和集中写:
int OwnedImpl::read(int fd, uint64_t max_length) {
if (max_length == 0) {
return 0;
}
constexpr uint64_t MaxSlices = 2;
RawSlice slices[MaxSlices];
//获取足够存储max_length数量的内存块
const uint64_t num_slices = reserve(max_length, slices, MaxSlices);
struct iovec iov[num_slices];
uint64_t num_slices_to_read = 0;
uint64_t num_bytes_to_read = 0;
//设置RawSlice和iovec对应,以传递readv调用
for (; num_slices_to_read < num_slices && num_bytes_to_read < max_length; num_slices_to_read++) {
iov[num_slices_to_read].iov_base = slices[num_slices_to_read].mem_;
const size_t slice_length = std::min(slices[num_slices_to_read].len_,
static_cast<size_t>(max_length - num_bytes_to_read));
iov[num_slices_to_read].iov_len = slice_length;
num_bytes_to_read += slice_length;
}
ASSERT(num_slices_to_read <= MaxSlices);
ASSERT(num_bytes_to_read <= max_length);
//调用readv读取
auto& os_syscalls = Api::OsSysCallsSingleton::get();
const ssize_t rc = os_syscalls.readv(fd, iov, static_cast<int>(num_slices_to_read));
if (rc < 0) {
return rc;
}
uint64_t num_slices_to_commit = 0;
uint64_t bytes_to_commit = rc;
ASSERT(bytes_to_commit <= max_length);
//根据读取到的实际数据量调整slices数组
while (bytes_to_commit != 0) {
slices[num_slices_to_commit].len_ =
std::min(slices[num_slices_to_commit].len_, static_cast<size_t>(bytes_to_commit));
ASSERT(bytes_to_commit >= slices[num_slices_to_commit].len_);
bytes_to_commit -= slices[num_slices_to_commit].len_;
num_slices_to_commit++;
}
ASSERT(num_slices_to_commit <= num_slices);
//将读取数据添加进缓存区
commit(slices, num_slices_to_commit);
return rc;
}
int OwnedImpl::write(int fd) {
constexpr uint64_t MaxSlices = 16;
RawSlice slices[MaxSlices];
//返回evbuffer内存存储的指针数组
const uint64_t num_slices = std::min(getRawSlices(slices, MaxSlices), MaxSlices);
struct iovec iov[num_slices];
uint64_t num_slices_to_write = 0;
//根据evbuffer的数据量调整iovec数组
for (uint64_t i = 0; i < num_slices; i++) {
if (slices[i].mem_ != nullptr && slices[i].len_ != 0) {
iov[num_slices_to_write].iov_base = slices[i].mem_;
iov[num_slices_to_write].iov_len = slices[i].len_;
num_slices_to_write++;
}
}
if (num_slices_to_write == 0) {
return 0;
}
//调用writev写数据
auto& os_syscalls = Api::OsSysCallsSingleton::get();
const ssize_t rc = os_syscalls.writev(fd, iov, num_slices_to_write);
//若写数据成功,从缓冲区删除发送成功的数据
if (rc > 0) {
drain(static_cast<uint64_t>(rc));
}
return static_cast<int>(rc);
}
其他的函数都很简单,只要知道evbuffer的接口就很容易看懂实现。
WatermarkBuffer
WatermarkBuffer是对OwnedImpl的再次封装,添加了两个水位检测变量和回调,当OwnedImpl中evbuffer的数据量低于低水位时会回调低水位回调函数,高水位回调于此类似。
std::function<void()> below_low_watermark_;
std::function<void()> above_high_watermark_;
// Used for enforcing buffer limits (off by default). If these are set to non-zero by a call to
// setWatermarks() the watermark callbacks will be called as described above.
uint32_t high_watermark_{0};
uint32_t low_watermark_{0};
ZeroCopyInputStreamImpl
ZeroCopyInputStreamImpl内部包含了一个Buffer::InstancePtr buffer_,其功能类似于一个迭代器,提供了对buffer_的一种访问方式,
uint64_t position_{0};
uint64_t byte_count_{0};
position指向evbuffer的evbuffer_chain的相对起始位置,
byte_count 表示当前Next过的所有byte数量。