Muduo库中的Buffer设计

Muduo库中的Buffer类设计

  • 非阻塞网络编程中应用层buffer是必须的
    原因:非阻塞IO的核心思想是避免阻塞在read()或write()或其他IO系统调用上,这样可以最大限度复用thread-of-control,让一个线程能服务于多个socket连接。IO线程只能阻塞在IO-multiplexing函数上,如select()/poll()/epoll_wait()。这样一来,应用层的缓冲是必须的,每个TCP socket都要有input buffer和output buffer。
  • TcpConnection必须有output buffer
    原因:使程序在write()操作上不会产生阻塞,当write()操作后,操作系统一次性没有接受完时,网络库把剩余数据则放入output buffer中,然后注册POLLOUT事件,一旦socket变得可写,则立刻调用write()进行写入数据。——应用层buffer到操作系统buffer
  • TcpConnection必须有input buffer
    原因:当发送方send数据后,接收方收到数据不一定是整个的数据,网络库在处理socket可读事件的时候,必须一次性把socket里的数据读完,否则会反复触发POLLIN事件,造成busy-loop。所以网路库为了应对数据不完整的情况,收到的数据先放到input buffer里。——操作系统buffer到应用层buffer

总结:input和output都是针对应用层来说的

Buffer设计

1、Buffer要求

  • 对外表现为一块连续的内存(char*,len);
  • 其size()可以自动增长,以适应不同大小的消息;
  • 内部以vector来保存数据,并提供相应的访问函数。

Buffer就像一个queue,从尾部写入数据,从头部读出数据。

2、Buffer模型

TcpConnection会有两个Buffer成员,input buffer和output buffer。

  • input buffer:TcpConnection会从socket读取数据,然后写入input buffer(这一步实际上是由Buffer::readFd()完成的),客户代码从input buffer读取数据。
  • output buffer:客户代码写入output buffer(这一步实际上是由TcpConnection::send()完成的),TcpConnection从output buffer读取数据并写入socket。

总结:input和output是针对客户代码而言,客户代码从input读,往output写。TcpConnection的读写正好相反。

图1 Buffer示意图

3、Muduo Buffer的数据结构

Buffer的内部是一个vector of char,它是一块连续的内存。Buffer有两个数据成员:readIndex和writeIndex,指向该vector中的元素。数据结构如下:
图2 Buffer数据结构
两个indices把vector的内容分为三块:prependable、readable、writable,各块大小计算如下:

  • prependable=readIndex
  • readable=writeIndex-readIndex
  • writable=size()-writeIndex
    Muduo Buffer里有两个常数kCheapPrepend和kInitialSize,定义了prependable和writeable的初始大小(readable的初始大小为0)。在初始化之后,Buffer的数据结构如下:
    图3 Buffer初始化数据结构

4、Muduo Buffer的操作

基本的read-write cycle

向Buffer写入200字节,那么布局如下:
图4 向Buffer写入200字节
现在有人从Buffer读入了50字节,那么布局如下:
图5 向Buffer读入50字节
接下来一次性读入150字节,则readIndex和writeIndex返回原位以备新一轮使用,布局如下所示:

图6 一次性读完数据

自动增长

Muduo Buffer不是固定长度的,它是可以自动增长的,这是使用vector的直接好处。即当我们需要写入数据大于size()时,那么Buffer会自动增长以容纳全部数据。由于vector重新分配了内存,原来指向它元素的指针会失效,这就是为什么readIndex和writeIndex是整数下标而不是指针。

size()与capacity()

使用vector的另一个好处是它的capacity()机制减少了内存分配的次数。即可以预先分配大的容量,当size不足时,不需要重新分配内存,可以直接进行扩充size。

内部腾挪

有时候经过若干次读写,readIndex移到了比较靠后的位置,留下了巨大的prependable空间,当这个时候,我们想写入数据,而writable空间不足,怎么办?muduo Buffer在这种情况下不会重新分配内存,而是先把已有的数据移到前面去,占用prependable空间,腾出writable空间。这样做原因是,如果重新分配内存,反正也是把数据拷贝到新分配的内存区域,代价只会更大。

prepend

muduo Buffer提供了prependable空间,让程序以很低的代价在数据前面添加几个字节。比如,程序以固定的4个字节表示消息的长度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章