Muduo中Buffer的设计和实现
1.为什么non-blocking 网络编程中应用层的buffer是必须的
non-blocking IO
核心思想就是避免阻塞在read & write
或者其他的IO系统调用上面,让一个线程能够服务多个socket连接。这样应用层次的缓冲是必须的。
TCPConnection有outputbuffer
场景:发送100KB的数据但是
write()
系统调用接受到了80KB的数据,就是TCP内核拥有一个发送的缓冲区,就是这次接受到了80KB,但是总不能在原地等待吧。那么剩下的20KB肿么办呢?其实对于应用程序来说,它需要关心的不是发送分几次,这次发送完了没。它只需要知道它调用了发送send(),网路库能够帮忙完成就行了。
网络库就有必要设置一个应用层的buffer了,这次的20KB换存在Buffer中,注册POLLOUT事件,一旦socket可写就往内核中发送数据。写完了就停止POLLOUT.
如果Buffer中还是有待发送的数据,但是程序想要关闭(他认为调用了send()就发送完了),但是网络库不能直接关闭,需要等待数据发送完成,TCP处于半连接状态
TCPConnection有inputbuffer
TCP是无边界的字节流协议,接收方需要处理 数据接受不能构成完整消息和一次性接受两条消息的情况。
网络库在处理socket可读事件时候尽量一次性读取完成(这里使用的是LT模式),如果不读取完成会反复触发EPOLLIN事件,造成busy-loop.将buffer中的消息提取出来构成完整的消息再发送,当然TCP分包的情况在上一篇文章中Muduo ChatRoom介绍过了。
2.Muduo中Buffer设计
buffer中的inputbuffer & output buffer是针对于客户代码而言的,客户代码从inputbuffer读取, 往outputbuffer中写。tcpconnection的读写正好是相反的。
怎么设计并且使用缓冲区?
- 我们希望系统调用的次数越少越好(那么缓冲区就要大一点才能一次性读取多一点)
- 我们希望减少内存占用(那么缓冲区就要小一点,这样并发连接的总内存占用的少)
要满足这两个需求似乎是有点矛盾的,缓冲区究竟多大呢?
Muduo使用了一个很巧妙的方式:利用
readv
函数➕栈空间解决这个问题。(具体可以看我写的高级IO这一篇文章)
- 在栈空间准备65536字节(64KB)的buf,利用
readv
读取socket上的数据到iovec
上面,iovec
分别指向Muduo Buffer
和栈上面的buffer
.- 如果读的数据不多,直接放在
muduo buffer
,如果读的数据很多就放到栈空间,然后append
到muduo buffer
上面,这样就完美的解决了既然muduo buffer能动态调整大小,为什么还要这样做呢?
如果不利用栈空间的话,那么调用
read
的次数就太多了,利用栈空间就能一次性读取很多啦🤪
实现细节分析
底层的存储使用
vector
进行存储,可以进行动态的扩容操作使用
readIdx
,writeIdx
指针来表示可读和可写的区域缓冲区的模型: +-------------------+------------------+------------------+ | prependable bytes | readable bytes | writable bytes | | | (CONTENT) | | +-------------------+------------------+------------------+ | | | | 0 <= readerIndex <= writerIndex <= size
- 增加了前置空间用于序列化等的时候进行操作头
从tcp读数据到buffer中
- 使用了
readv
进行分散的读,将数据读到buffer
&栈空间
- 从栈上面开辟大片的空间,如果不需要就用
buffer
,需要则将栈上暂存的写到buffer
中