muduo网络库源码复现笔记(二十五):缓冲区Buffer类

Muduo网络库简介

muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕。它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程网络应用程序。
muduo网络库的核心代码只有数千行,在网络编程技术学习的进阶阶段,muduo是一个非常值得学习的开源库。目前我也是刚刚开始学习这个网络库的源码,希望将这个学习过程记录下来。这个网络库的源码已经发布在GitHub上,可以点击这里阅读。目前Github上这份源码已经被作者用c++11重写,我学习的版本是没有使用c++11版本的。不过二者大同小异,核心思想是没有变化的。点这里可以看我的源代码。从笔记十七开始记录muduo的net库的实现过程。如果你需要看一下基础库(base)的复现过程,可以点击这里:muduo的base库实现过程。而网络库的笔记在这里:
muduo网络库源码复现笔记(十七):什么都不做的EventLoop
muduo网络库源码复现笔记(十八):Reactor的关键结构
muduo网络库源码复现笔记(十九):TimeQueue定时器
muduo网络库源码复现笔记(二十):EventLoop::runInloop()函数和EventLoopThread类
muduo网络库源码复现笔记(二十一):Acceptor类、InetAddress类、Sockets类、SocketsOps.cc
muduo网络库源码复现笔记(二十二):TcpServer类与TcpConnection初步
muduo网络库源码复现笔记(二十三):TcpConnection断开连接
muduo网络库源码复现笔记(二十四):实现多线程服务器

Buffer类

1 Buffer类的作用

这一节讲述的Buffer类实现了用户应用层缓冲区。muduo是一个非阻塞的IO模型,也就是说,在调用read和write时不会阻塞。考虑这样一个场景:程序想通过TCP连接发送100kb的数据,但通过write向内核缓冲区写入数据时,系统只接受了80kb,那剩下的20kb数据该如何处理呢?这时本次实现的用户缓冲区就派上用场了,用户缓冲区存储了剩余20kb数据,注册POLLOUT事件,等待机会将数据发送完毕,然后取消关注POLLOUT事件。这样的缓冲区,我们称之为output buffer。
既然有output buffer,也就有input buffer。由于TCP是一个无边界的字节流协议,时常会有这种情况发生:发送方发送2kb消息,而接收方分两次才收到,分别受到500B、1500B;或者接收方分三次才收到,分别受到1000B、500B、500B;网络库必须一次性将这些数据读好,等受到一条完整的消息时再通知程序的业务逻辑。

2 Buffer操作

如前所述,一个Tcp连接要有两个缓冲区,分别是input buffer和output buffer。如下面Buffer的私有成员所示,vector用于存储数据,初始化为1024字节长度。readerIndex_和writeIndex_分别是两个下标,初始状态他们的值相等(即此时没有要发送数据),readerIndex之前保留了8个字节,属于预留空间。这里下标的reader和write是相对用户说的,对于内核来说操作刚好反过来,这一点需要读者理解体会。

private:
	std::vector<char> buffer_; //replace array
	size_t readerIndex_;  //position of data to read(will write to kernel)
	size_t writerIndex_;  //position of data write(read fron kernel)

如图片中所示,当内核向buffer写入200字节后,writeIndex向后移动200字节,readIndex_和writeIndex_之间的区域便是output buffer,区域中的数据便是用户读取到的数据,可以被写入内核发送,writeIndex_之后的区域是input buffer,等待从内核中读取数据写入。
基本操作
若向buffer中读取50字节,则readIndex向后移动50字节,表示那50字节被用户读取过了。
Buffer还可以扩容,若这时想buffer写入1000字节,buffer判断当前空间不够,会resize。

3 readFd函数

用户在使用Buffer接受数据实际上只会使用readFd函数,这个函数首先预备了一个64k的栈上缓冲区extrabuf,然后使用readv函数读取数据到iovec(iovec分别指定了buffer和extrabuffer)中,判断若buffer容量足够,则只需移动writerIndex_,否则使用append成员函数将剩余数据添加到buffer中(会执行扩容操作)。

ssize_t Buffer::readFd(int fd,int* savedErrno)
{
    
    
	char extrabuf[65536];
	struct iovec vec[2];
	const size_t writable = writableBytes();
	//first buffer
	vec[0].iov_base = begin() + writerIndex_;
	vec[0].iov_len = writable;
	//second buffer
	vec[1].iov_base = extrabuf;
	vec[1].iov_len = sizeof extrabuf;

	const ssize_t n = sockets::readv(fd,vec,2);
	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;
}

猜你喜欢

转载自blog.csdn.net/MoonWisher_liang/article/details/107700456
今日推荐