muduo源码阅读(四):Buffer的设计

Buffer设计

muduo的图示:
muduo源码阅读(四):Buffer的设计_第1张图片

buffer的必要性

muduo的IO模型采用的是阻塞式的,因此线程只能阻塞在 seletc/poll/epoll_wait 当中,而不能阻塞的write/read函数中,这么一来,应用层的缓冲是必须的。每一个TCP socket都要配备一个input buffer和一个 output buffer。

具体场景举例 outputbuffer的必要性
目的:应用程序要发送100KB数据,
方法:调用write(),但是操作系统只一次接受了80KB(受tcp的adertised window控制),而因为不能阻塞,程序也不能在此等待,也并不知道操作系统何时来接收剩余的20KB,此时该如何做?

我们要让应用层不关心数据最后是如何发出去的,而只是调用Tcpconnection::send去将要发送的数据放到outputbuffer中,然后注册POLLOUT事件。然后剩下的事情由网络库接管,网络会在channel中关注pollout事件,一旦socket可读,就会将outputbuffer中的数据写入,如果后续还有50kb数据过来,会先append到20kb后,待socket可写时再一起写入。

除此之外,在关闭连接时,也要等outputbuffer中的数据都发出去以后,才能断开连接。
(在Tcpconnectio::shutdown中没有直接关闭TCP连接)

inputbuffer必要性 粘包
假设,发送方此时发送了2KB数据,则接收的情况可能是:

  1. 分两次收到,一次600B,一次1400B
  2. 分两次收到,一次1400B,一次600B
  3. 分三次收到,一次600B,一次800B,一次600B,
    还有很多很多的无数种可能性…

解决的方案:接收到的数据先放到inputbuffer里面,等构成了一个完整的数据包,载通知业务程序读取。(如何判断当前是一个完整的数据包,这是由业务程序决定的,有一个codec的函数)

Buffer的功能需求

更加侧重易用性,不具有线程安全性,因为任何一个buffer不会暴露给其他线程。

1. 展现给用户的是一块连续的内存,方便用户编写。
2. 缓冲区的大小可以自动增长,以适应不同大小的消息
3. 内部用std::vector来保存数据,并提供相应的访问函数

谁是直接使用buffer的:TcpConnection用户代码
inputbuffer: 读者 : 客户端应用层代码 写入者: TcpConnection(readfd)
outputbuffer: 读者 : TcpConnection 写入者: 客户端应用代码(send)

巧妙的创新点

  1. extrabuf:如果,buffer的初始内存太大,那么容易造成内存浪费;如果buffer的内存不够大,那么一次read可能不够读所有数据,需要多次read。这样的话如何才能找到平衡点呢?muduo采用了extrabuf,这个buffer是分配在了临时栈上,当函数返回时会自动释放。具体的操作过程是,muduo调用的是readv函数,如果数据过大,可以一次性读取并将数据分别放到buffer和extrabuf,然后再根据需要扩充buffer,然后将extrabuf中的数据放到buffer中(append),函数返回,extrabuf释放,所有数据留在了buffer中。

  2. prependable:这个prependable的作用就是在readable前面留了几个字节,一开始是8个字节,它的作用是如果我需要在数据流的前面加几个字节,那么就可以直接在这一块加上,而不需要对整个buffer进行移动。比如:一开始我不知道接受了多少个字节,接收完以后需要在数据头加上字节数。这样就可以直接在prependable上面加上去。

  3. 采用vector 缓冲区的设计采用vector ,vector可以动态的分配内存,并且,采用的是capcity()
    机制,就是说第一次分配了这个区间内存大小以后, 就不会变小,后面可以减少分配的次数。

你可能感兴趣的:(muduo)