目录
- 为什么需要缓冲区
- 自动增长的缓冲区
- 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 read
char 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_。