这一次咱们来剖析下muduo
中Buffer
的作用,咱们晓得,当咱们客户端向服务器发送数据时候,服务器就会读取咱们发送的数据,而后进行一系列解决,而后再发送到其余中央,在这里咱们设想一下最简略的EchoServer
服务器,客户端建设一个连贯,当前服务器和客户端之间的通信都是通过这个connfd
发送和承受数据,于是每一个connfd
都应该有一个本人buffer
,当咱们发送数据太快,服务器发送的太慢,则服务器会将待发送的数据这个buffer
中,所以这就是这个类的作用。咱们先看下buffer
的构造是什么:
咱们这里次要针对connfd
这个对应的channel
进行剖析,首先上图是buffer
的初始状态,后面8
个字节中示意该buffer
的大小,初始大小为1024
。当客户端发送数据给服务器,同时若服务器承受迟缓,则会向buffer
中开始写数据,则writerIndex_
会向右挪动,如果此时挪动到如下模式:
则此时缓冲区能够读的数据为writerIndex_ - readerIndex_
,能够写的数据为buffer_.size() - writerIndex_
。这时候当服务器有多余资源进行读操作,就能够去缓冲区读数据了,如果这时候的状态为如下:
这就是常见的几个状态,上面咱们去看几个重点的函数:
// 把onMessage函数上报的buffer内容转为string std::string retrieveAllAsString() { return retrieveAsString(readableBytes()); // 利用可读取数据的长度 } // 可读的数据 就是寄存的是即发送的数据 size_t readableBytes() const { return writerIndex_ - readerIndex_; } std::string retrieveAsString(size_t len) { // 从可读数据开始地位,长度为len的char结构为一个string std::string result(peek(), len); retrieve(len); // 下面一句把缓冲区中可读的数据,曾经读取进去,这里必定要对缓冲区进行复位操作 return result; } // 将缓冲区len的长度进行复位 void retrieve(size_t len) { // 示意还没有读完数据 if (len < readableBytes()) { readerIndex_ += len; // 利用只读取了刻度缓冲区数据的一部分,就是len,还剩下readerIndex_ += len -> writerIndex_ } else // len == readableBytes() { retrieveAll(); } }
以上是根本的操作,上面的2个函数很重要,一个是向connfd
写数据,一个是读数据,对于一个TcpConnection
而言,当有数据来的时候,回去调用handleRead
回调函数。咱们晓得muduo
设置的每次读取的大小为65536
字节,当缓冲区可写的数据大小大于65536
,就会间接将读到的数据写入到缓冲区中,但当缓冲区的可写数据大小小于65536
的时候,就会将残余数据先写到一个额定的空间
ssize_t Buffer::readFd(int fd, int* saveErrno){ char extrabuf[65536] = {0}; // 栈上的内存空间 64K struct iovec vec[2]; // 这是buffer可写的数据 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; // 去百度下readv const ssize_t n = ::readv(fd, vec, iovcnt); if (n < 0) { *saveErrno = errno; } else if (n <= writable) // Buffer的可写缓冲区曾经够存储读出来的数据了 { writerIndex_ += n; } // extrabuf 也写了数据 else { writerIndex_ = buffer_.size(); append(extrabuf, n - writable); // writerIndex_开始写 n - writable大小的数据 } return n;}
这里奇妙的应用了一个readv
函数,能够通过依照大小主动写到不同的中央。其中当extrabuf
也写了数据,就会调用append
函数。
// 要写len长度的数据 void ensureWriteableBytes(size_t len) { if (writableBytes() < len) { makeSpace(len); // 扩容函数 } } // 向缓冲区增加数据 void append(const char *data, size_t len) { ensureWriteableBytes(len); std::copy(data, data+len, beginWrite()); writerIndex_ += len; }
留神到有一个makeSpace
函数,其中有一个留神点,比方当如下这种状态的时候:
此时readerIndex_
后面有一部分其实曾经被读完了,是空的数据,所以makeSpace
函数思考了这一点,采纳内存重组的形式,将readerIndex_
向前挪动到kCheapPrepend
处,而后就能够让空余的内存挨在一起
void makeSpace(size_t len) { if (writableBytes() + prependableBytes() < len + kCheapPrepend) { buffer_.resize(writerIndex_ + len); } else { size_t readalbe = readableBytes(); std::copy(begin() + readerIndex_, begin() + writerIndex_, begin() + kCheapPrepend); readerIndex_ = kCheapPrepend; writerIndex_ = readerIndex_ + readalbe; } }
当向connfd
发送数据的时候就比较简单了,间接将可读的数据发送给进来就行
// 通过fd发送数据ssize_t Buffer::writeFd(int fd, int* saveErrno){ ssize_t n = ::write(fd, peek(), readableBytes()); if (n < 0) { *saveErrno = errno; } return n;}
本人的网址:www.shicoder.top
欢送加群聊天 452380935
本文由博客一文多发平台 OpenWrite 公布!