目录
- 为什么需要缓冲区
- 自动增长的缓冲区
- buffer数据结构
- buffer类
写详细比较费时间,就简单总结下。
总结自Linux 多线程服务端编程:使用 muduo C++ 网络库
Muduo网络编程:
IO-multiplex+non-blocking
为什么需要缓冲区
Non-blocking IO 的核心思想是避免阻塞在 read() 或 write() 或其他 IO 系统调
用上,所以应用层的缓冲是必须的,每个 TCP socket 都要有 input buffer
和 output buffer。
自动增长的缓冲区
一方面希望减少系统调用,所以缓冲区越大越好
另一方面希望减少内存占用,如果缓冲区很大而连接很多,将会占用大量内存空间。而大多数时候缓冲区的使用率又很低。
buffer数据结构
buffer类
Muduo Buffer的size()是自适应的,一开始的初始值是1k。
抽取相关代码:
class Buffer:public muduo::copyable{public:static const size_t kCheapPrepend = 8;static const size_t kInitialSize = 1024;explicit Buffer(size_t initialSize = kInitialSize): buffer_(kCheapPrepend + initialSize),readerIndex_(kCheapPrepend),writerIndex_(kCheapPrepend){...}...std::vector<char> buffer_;
}
vector数组,保证了可以扩展,初始化了1k可用空间,并在头部预留了1Byte。
具体做法是,在栈上准备一个 65536 字节的 extrabuf,然后利用 readv() 来读取数据。
iovec 有两块,第一块指向muduo Buffer 中的 writable 字节,另一块指向 栈上的 stackbuf。
这样如果读入的数据不多,那么全部都读到Buffer 中去了;如果长度超过 Buffer 的 writable 字节数,就会读到栈上的 extrabuf 里,然后程序再把extrabuf 里的数据 append 到 Buffer 中。
这么做利用了临时栈上空间,避免开巨大 Buffer 造成的内存浪费,也避免反复 调用 read() 的系统开销(通常一次 readv()系统调用就能读完全部数据)。
代码相关实现:Buffer::readFd()
ssize_t Buffer::readFd(int fd, int* savedErrno)
{// saved an ioctl()/FIONREAD call to tell how much to readchar extrabuf[65536];struct iovec vec[2];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;// when there is enough space in this buffer, don't read into extrabuf.// when extrabuf is used, we read 128k-1 bytes at most.const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;const ssize_t n = sockets::readv(fd, vec, iovcnt);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;
}
首先写buffer,写不下的写extrabuf,然后把extrabuf的写入buffer,具体函数是append。
void append(const char* /*restrict*/ data, size_t len){ensureWritableBytes(len);std::copy(data, data+len, beginWrite());hasWritten(len);}
ensureWritableBytes检查buffer是否有空余空间可以被写入,没有就vector.resize(len),扩展len。然后通过std::copy,把extrabuf上的内存copy到buffer上。最后更新writerIndex_。