Buffer的功能需求
设计buffer可以从易用性和性能两方面考虑,muduo的buffer更偏向于易用性。
- 对外表现是一块连续的内存
(char* p, int len)
- 其
size()
可以自动增长,不是固定大小的数组 - 内部以
std::vector<int>
来保存数据 - buffer更像一个queue,从尾部写入数据,从头部读取数据
input buffer
:连接从socket中读取数据,写入input buffer
;客户代码从中读取数据output buffer
:客户代码把数据写入output buffer
,连接从output buffer
中读数据并写入socket
muduo/base基础库
在学习muduo如何实现Buffer之前,先阅读Buffer.h
头文件中所需要的依赖。
types.h
基本类型的声明,如muduo::string
;
具体有:
inline void memZero(void* p, size_t n); //作为static_cast<>或const_cast<>的安全版本 template<typename To, typename From> inline To implicit_cast(From const &f) //当upcast使用implicit_cast<>,downcast时static_cast<>不再安全,这里使用 //dynamic_cast<>来检查downcast是否合法 //不支持RTTI,如 //if(dynamic_cast<subclass>(foo)) HandleSubclassObj(foo); template<typename To, typename From> inline To down_cast(From* f)
StringPiece.h
传递C风格的字符串参数给函数,知乎有个相关的提问与回答点击查看,StringPiece
是某公司使用的一个类。
class StringArg { public: StringArg(const char* str) : str_(str) { } StringArg(const string& str) : str_(str.c_str()) { } const char* c_str() const { return str_; } private: const char* str_;
提供non-explicit构造函数给使用者,可以传递const char*
或string
#ifndef MUDUO_BASE_STRINGPIECE_H #define MUDUO_BASE_STRINGPIECE_H namespace muduo { { class StringPiece { private: const char* ptr_; int length_; public: //default StringPiece() : ptr_(NULL), length_(0) { } //参数为字符指针 StringPiece(const char* str) : ptr_(str), length_(static_cast<int>(strlen(ptr_))) { } //参数为无符号字符指针; reinterpret_cast临时隐藏类型,使用时要将其转换为原型 //具有非常强的转换能力 StringPiece(const unsigned char* str) : ptr_(reinterpret_cast<const char*>(str)), length_(static_cast<int>(strlen(ptr_))) { } StringPiece(const string& str) : ptr_(str.data()), length_(static_cast<int>(str.size())) { } StringPiece(const char* offset, int len) : ptr_(offset), length_(len) { } //string类常见成员 const char* data() const { return ptr_; } int size() const { return length_; } bool empty() const { return length_ == 0; } const char* begin() const { return ptr_; } const char* end() const { return ptr_ + length_; } void clear() { ptr_ = NULL; length_ = 0; } void set(const char* buffer, int len) { ptr_ = buffer; length = len; } void set(const char* str) { ptr = str; length_ = static_cast<int>(strlen(str)); } //运算符成员函数[], ==, char operator[](int i) const { return _ptr[i]; } bool operator==(const StringPiece& x) const { return (x.length_ == length_) && (memcmp(x.ptr_, ptr_, length_) == 0); } bool operator!=(const StringPiece& x) const { return !(*this == x); } //比较成员函数 int compare(const StringPiece& x) const { ... } //转换成string类型 string as_string() const {...} void CopyToString(string* target) { ... } //判断起始字符 bool start_with(const StringPiece& x) const { ... } }; } // namespace muduo //允许String Pieces被记录到日志中 std::ostream& operator<<(std::ostream o&, const muduo::StringPieces piece); #endif
muduo/net网络库
Buffer.cc和Buffer.h
1)特点:封装vector作为缓冲区,因为vector为一块连续空间,且其本身具有自动增长的性质,迭代器为原始指针,使用起来较为方便
2)分布:缓冲区分为三个部分
prependable:大小为readerIndex
readable:大小为writerIndex - readerIndex
writeable:大小为size() - writerIndex
初始时,readable == 0,readIndex = writeIndex = 8;
3)检索:Buffer类通过findCRLF
和findEOL
成员函数在readerable
区域检索\r\n
和EOL
4)调整区域:
- 通过成员函数
hasWritten
和unwrite
调整可读/可写区域的大小,如果向Buffer写入200字节,那么writeIndex += 200
,readable == 200
,writable == 824
; - 成员函数
read()
和retrieve()
读入50字节,readIndex += 50
,readable == 150
,writable == 824
;当一次性读完时,调用retrieveall()
将Buffer
重置
5)自动增长:如果客户代码一次性写入1000字节,而当前可写字节小于这个数(prependable+writable),那么buffer就会自动增长以容纳全部数据。
6)内部挪腾:当可写字节满足需求,但是writable大小不足时,移动readable,腾出位置
7)前方添加:提供prependable,让程序能以很小的代价在数据前面添加几个字节。比如说要序列化一个消息,但是并不知道消息长度,于是不断append()直到序列化完成,此时可以通过readable区域变化求出消息长度,在prependable中用4个字节存储。
8)读取内容:利用readfd()
,在栈上开辟一块65536字节额外缓冲区,利用readv()来读。
class Buffer : public muduo::copyable { private: //1) 用一个vector来维护一个缓冲区 std::vector<char> buffer_; size_t readerIndex_; size_t writerIndex_; static const char kCRLF[]; private: char* begin() { return &*buffer_.begin(); } //使用vector自身的迭代器,begin()返回缓冲区起始元素 char* beginWrite() { return begin() + writeIndex_; } //获得writeable区的起始元素 public: static const size_t kCheapPrepend = 8; //定义prepenable初始大小为8 static const size_t kInitialSize = 1024; //定义writeable初始大小为1024 //2) 求缓冲区三个部分的大小 size_t readableBytes() const { return writerIndex_ - readerIndex_; } size_t writableBytes() const { return buffer_.size() - writerIndex_; } size_t prependableBytes() const { return readerIndex_; } const char* peek() const { return begin() + readerIndex_; } //求readerable的头部指针 //3) 检索"/r/n"和EOL const char* findCRLF() const { const char* crlf = std::search(peek(), beginWrite(), kCRLF, kCRLF+2) //检索范围是readable区域,搜索的元素范围是[kCRLF,kCRLF+2] return crlf == beginWrite() ? NULL : crlf; //若search返回尾部迭代器则没有找到 } //重载findCRLF,可以自定义检索起始位置 const char* findCRLF(const char* start) const { assert(peek() <= start); assert(start <= beginWrite()); ... } const char* findEOL() const { ... } //在readerable中搜索换行符 const char* findEOL(const char* start) const { ... } private: //4) 写入和未写入移动writerIndex来调整可读/可写区域的大小 void hasWritten(size_t len) { assert(len <= writableBytes()); writerIndex += len; } void unwrite(size_t len) { assert(len <= readableBytes()); writerIndex_ -= len; } //从缓冲区中读取数据 void retrieve(size_t len) { ... } void retrieveAll() { readerIndex_ = kCheapPrepend; writerIndex_ = kCheapPrepend; } void append(const char* data, size_t len) { ensureWritableBytes(len); std::copy(data, data+len, beginWrite()); hasWritten(len); } //5) 自动增长 void ensureWritableBytes(size_t len) { if (writableBytes() < len) { makeSpace(len); } assert(writableBytes() >= len); } //6) 内部腾挪 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(first, last, destfirst) 要拷贝元素的首地址、尾地址和拷贝目的地首地址 std::copy(begin()+readerIndex_, begin()+writerIndex_, begin()+kCheapPrepend); readerIndex_ = kCheapPrepend; writerIndex_ = readerIndex_ + readable; assert(readable == readableBytes()); } } }; //8) 读取内容 ssize_t Buffer::readFd(int fd, int* savedErrno) { char extrabuf[65536]; //在栈上开辟一块额外的缓冲区 struct iovec vec[2]; const size_t writable = writableBytes(); //iovec第一块指向buffer中的writable,第二块指向extrabuf vec[0].iov_base = begin()+writerIndex_; vec[0].iov_len = writable; vec[1].iov_base = extrabuf; vec[1].iov_len = sizeof extrabuf; //当空间足够时,即 n < writable,不使用extrabuf //否则读到extrabuf,然后再把extrabuf中的数据append()到Buffer中 const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1; const ssize_t n = sockets::readv(fd, vec, iovcnt); //readv返回读到的总字节数n if (n < 0) { *savedErrno = errno; //存储异常 } else if (implicit_cast<size_t>(n) <= writable) { writerIndex_ += n; } else { writerIndex_ = buffer_.size(); append(extrabuf, n - writable); } return n; }