muduo库的学习6---Buffer的基本设计

1.使用应用层buffer的原因

这个陈硕对应的书里面讲地很清楚,见书P205 7.4.2

分为两点

①为什么需要output buffer

②为什么需要input buffer


2.Class Buffer

class Buffer : public muduo::copyable
{
/*******Function Member***********/

//general use
  char* peek();
  char* beginWrite();

//clear the buffer
  int32_t readInt();
  int32_t peekInt();
  void retrieve(size_t len);

//fill in the buffer
  ssize_t readFd(int fd, int* savedErrno);
  void append(const char* data, size_t len);
  void hasWritten(size_t len);
  
//prepend the buffer
  void prepend(const void*  data, size_t len);
  
/********Data Member************/
  std::vector<char> buffer;
  size_t readerIndex;
  size_t writerIndex;

};


3.具体分析

(1)数据成员

①buffer

采用vertor<char>作为整个Buffer的底层实现

好处是可以利用vector的一些特性进行一些方便的resize操作等等

②XXX Index

这个就是用来指示用的。

看完书中对应的7.4.4与7.4.5中的图,很好理解。

(2)通用函数

①peek

取名类似于c文件操作里面的peek,那里面是返回当前下标的位置。

这里Buffer::peek()返回的是内存中eadable段开始的位置。

因为readerIndex是一个相对地址(相对于buffer的首地址),所以peek就是通过简单返回buffer.begin()+readerIndex实现的。

②beginWrite

与peek相对,返回的是buffer.begin()+writerIndex

(3)从缓冲区取出数据

①peekInt

memcpy取走数据

②retrieve

移动下标

③readInt

调用①peekInt和②retrieve

(4)向缓冲区写入数据

①hasWriten

移动下标

②append

先调用std::copy复制数据

再调用①hasWriten

③readFd

为了解决7.4.3 P208提到的问题,并且从socket fd读数据

在append外面又包装了一层。

(5)前向写入

为了解决消息长度开始位置问题,以很低的代价在前面添加几个字节

它自己就完成了A。移动小标 B。用std::copy向里面写内容


4.缓冲区大小设置问题

在P208页提到了,我们既希望有一个大的缓冲区(一次处理的数据更多,更少地内存分配与转移),

又希望缓冲区足够小(这样即使一个线程有许多并发连接也不会暂用太多内存)

muduo库通过Buffer::readFd函数试图解决这个问题。

(1)基本思路

初始化时,每个connection的缓冲区为1K大小,

readFd中会有一个栈内存的64K大小的extrabuf,

每次通过readv从connfd那边读取数据,先放入buffer之中,多出来的再放入extrabuf之中。

n = sockets::readv(connfd, vec, invcnt);

①n<buffer大小,直接更新buffer的writerIndex即可

②n>buffer大小,多出的部分在extrabuf里面,这个时候就调用append,将多出的部分放入buffer,buffer也实现了扩容。

(2)append函数

①ensureWritableBytes

首先验证增加的长度vector buffer能不能装下,如果不能就调用vector::resize到能

②std::copy

将数据复制到buffer中

③hasWritten

改变WriterIndex

(3)Buffer::readFd的好处

使用这个函数,①保证了大部分connection都只是有一个很小的buffer空间

②但是每次读取,有一个extrabuf在那里保证了几乎每次只需要一次read就能将connfd上来的数据读完。

而这个extrabuf相当于是该线程所有connection共用的,所以开销很小

③每次如果buffer空间不够几乎是多处了多少,进行那么多大小的扩容

且扩容后不再减少,以应对后面需要,减少内存分配次数。

(4)为什么extrabuf是threadlocal

因为在每个thread中是一个loop,loop里面采用IO Multiplexing,这样其实是每次最多有一个connection在使用extrabuf,线程安全。

如果是多个线程使用一个extrabuf,存在并发可能,线程不安全,需要加锁。

但是这样带来了编码难度的加大,带来的提升确并不是很多,所以不值得采用。还不如一个thread一个extrabuf。

(5)编码细节

①iovcnt

位于Buffer::readFd中,

const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;

这里,iovcnt指示了readv用几块地方存放数据,

这句话保证了,一次读取的最大大小为writable = 64K - 1 < extrabuf, iovcnt = 2, 这个时候一次可以读取sizeof(writable + extrabuf) = 128K -1


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值