餐饮品牌设计网站建设,公司网站建设方案详细方案,网站微信认证费用多少钱,企业资质证书查询官方网站Buffer类
Buffer类是自定义处理数据输入缓冲的类#xff0c;底层是vector char #xff0c;通过readIdx和writeIdx将缓冲区分为3个部分#xff0c;第一部分是预留的8字节已经读出的缓冲区字节数、第二部分是还未读出的部分、第三部分是可写的部分。 Buffer类的设计…Buffer类
Buffer类是自定义处理数据输入缓冲的类底层是vector char 通过readIdx和writeIdx将缓冲区分为3个部分第一部分是预留的8字节已经读出的缓冲区字节数、第二部分是还未读出的部分、第三部分是可写的部分。 Buffer类的设计是TcpConnection类设计的核心一个TcpConnection必须有一个inputBuffer和一个outputBuffer。 必须存在inputBuffer的原因Tcp是一个无边界的字节流协议接收方必须要处理收到的数据尚不构成一条完整的消息”和“一次收到两条消息的数据”等情况例如对应数据不完整的情况则收到的数据先放到inputBuffer里等构成一条完整的消息再通知程序的业务逻辑。 必须存在outputBuffer的原因考虑这样一种场景程序想通过Tcp连接发送100kb的数据但是在write调用中只接受了80kb数据那么还剩余的20kb数据应该存入outputBuffer中并注册POLLOUT事件如果写完了20kb的数据应该立即停止关注POLLOUT事件防止busy loop。因为对于应用程序而言它只管生成数据不关心数据是一次发送还是几次发送。
// 网络库底层的缓冲区类型定义
class Buffer
{
public:static const size_t kCheapPrepend 8;static const size_t kInitialSize 1024;explicit Buffer(size_t initalSize kInitialSize): buffer_(kCheapPrepend initalSize), readerIndex_(kCheapPrepend), writerIndex_(kCheapPrepend){}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_; }char *beginWrite() { return begin() writerIndex_; }const char *beginWrite() const { return begin() writerIndex_; }// 从fd上读取数据ssize_t readFd(int fd, int *saveErrno);// 通过fd发送数据ssize_t writeFd(int fd, int *saveErrno);private:// vector底层数组首元素的地址 也就是数组的起始地址char *begin() { return *buffer_.begin(); }const char *begin() const { return *buffer_.begin(); }std::vectorchar buffer_;size_t readerIndex_; //可读的起始位置size_t writerIndex_; //可写的起始位置
};读buffer的数据并输出为string主要通过retrieveAllAsString()和retrieveAsString()方法前者读出buffer所有的数据后者读出buffer长度为len字节的数据读出后对readIdx进行相应的维护 void retrieve(size_t len){if (len readableBytes()){readerIndex_ len; // 说明应用只读取了可读缓冲区数据的一部分就是len长度 还剩下readerIndexlen到writerIndex_的数据未读}else // len readableBytes(){retrieveAll();}}void retrieveAll(){readerIndex_ kCheapPrepend;writerIndex_ kCheapPrepend;}// 把onMessage函数上报的Buffer数据 转成string类型的数据返回std::string retrieveAllAsString() { return retrieveAsString(readableBytes()); }std::string retrieveAsString(size_t len){std::string result(peek(), len);retrieve(len); // 上面一句把缓冲区中可读的数据已经读取出来 这里肯定要对缓冲区进行复位操作return result;}buffer每次append数据的时候都会保证所有数据都能写入缓冲区核心是扩容操作扩容是先检查写入数据是否可以大于已读出的数据的部分预留部分可写部分如果大于则需要进行扩容操作否则先将未读的数据移动到readIdx起始的位置readidxkCheapPrepend, writeIdx readidxreadableBytes()然后将需要写入数据append到wireIdx后面
// 把[data, datalen]内存上的数据添加到writable缓冲区当中void append(const char *data, size_t len){ensureWritableBytes(len);std::copy(data, datalen, beginWrite());writerIndex_ len;}// buffer_.size - writerIndex_void ensureWritableBytes(size_t len){if (writableBytes() len){makeSpace(len); // 扩容}}void makeSpace(size_t len) //扩容操作{/*** | kCheapPrepend |xxx| reader | writer | // xxx标示reader中已读的部分* | kCheapPrepend | reader len |**/if (writableBytes() prependableBytes() len kCheapPrepend) // 也就是说 len xxx writer的部分{buffer_.resize(writerIndex_ len);}else // 这里说明 len xxx writer 把reader搬到从xxx开始 使得xxx后面是一段连续空间{size_t readable readableBytes(); // readable reader的长度std::copy(begin() readerIndex_,begin() writerIndex_, // 把这一部分数据拷贝到beginkCheapPrepend起始处begin() kCheapPrepend);readerIndex_ kCheapPrepend;writerIndex_ readerIndex_ readable;}}通过fd读数据到buffer以及通过buffer写入数据到fd从fd读数据稍显复杂利用了两个缓冲第一个缓冲是buffer自己可写部分的缓冲区第二个缓冲是在栈空间开辟 从fd读的数据首先写入第一个缓冲区(buffer自带) 如果数据大于这个缓冲区的大小则将剩余未写完的数据放入第二个缓冲区然后将这个缓冲区的数据调用append()方法添加到buffer的后面(可能发生扩容操作) 关于为什么采取双缓冲(buffer缓冲栈空间缓冲)的方法而不采取一个缓冲区(buffer缓冲)当调用read函数读取数据的时候从从内核的缓冲区读入到用户态的缓冲而采取一个缓冲的情况下如果读数据读到一半发现缓冲满了随后进行扩容操作但这个时间段内核态的数据可能还会源源不断的输入数据而这边没办法读入数据扩容之中)这使得操作系统没法腾出空间容纳更多的数据这样就得不偿失了。采取双缓冲的好处是从将所有数据一次性从内核态读入到用户态快速读完)这样操作系统也有可以容纳的空间给新到来的数据从而提高效率也间接的增大了系统的并发量。
/*** 从fd上读取数据 Poller工作在LT模式* Buffer缓冲区是有大小的 但是从fd上读取数据的时候 却不知道tcp数据的最终大小** description: 从socket读到缓冲区的方法是使用readv先读至buffer_* Buffer_空间如果不够会读入到栈上65536个字节大小的空间然后以append的* 方式追加入buffer_。既考虑了避免系统调用带来开销又不影响数据的接收。**/
ssize_t Buffer::readFd(int fd, int *saveErrno)
{// 栈额外空间用于从套接字往出读时当buffer_暂时不够用时暂存数据待buffer_重新分配足够空间后在把数据交换给buffer_。char extrabuf[65536] {0}; // 栈上内存空间 65536/1024 64KB/*struct iovec {ptr_t iov_base; // iov_base指向的缓冲区存放的是readv所接收的数据或是writev将要发送的数据size_t iov_len; // iov_len在各种情况下分别确定了接收的最大长度以及实际写入的长度};*/// 使用iovec分配两个连续的缓冲区struct iovec vec[2];const size_t writable writableBytes(); // 这是Buffer底层缓冲区剩余的可写空间大小 不一定能完全存储从fd读出的数据// 第一块缓冲区指向可写空间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, dont read into extrabuf.// when extrabuf is used, we read 128k-1 bytes at most.// 这里之所以说最多128k-1字节是因为若writable为64k-1那么需要两个缓冲区 第一个64k-1 第二个64k 所以做多128k-1// 如果第一个缓冲区64k 那就只采用一个缓冲区 而不使用栈空间extrabuf[65536]的内容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) // Buffer的可写缓冲区已经够存储读出来的数据了{writerIndex_ n;}else // extrabuf里面也写入了n-writable长度的数据{writerIndex_ buffer_.size();append(extrabuf, n - writable); // 对buffer_扩容 并将extrabuf存储的另一部分数据追加至buffer_}return n;
}// inputBuffer_.readFd表示将对端数据读到inputBuffer_中移动writerIndex_指针
// outputBuffer_.writeFd标示将数据写入到outputBuffer_中从readerIndex_开始可以写readableBytes()个字节
ssize_t Buffer::writeFd(int fd, int *saveErrno)
{ssize_t n ::write(fd, peek(), readableBytes());if (n 0){*saveErrno errno;}return n;
}