muduo网络库源码复现笔记(十二):base库的LogStream.h

Muduo网络库简介

muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕。它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程网络应用程序。
muduo网络库的核心代码只有数千行,在网络编程技术学习的进阶阶段,muduo是一个非常值得学习的开源库。目前我也是刚刚开始学习这个网络库的源码,希望将这个学习过程记录下来。这个网络库的源码已经发布在GitHub上,可以点击这里阅读。目前Github上这份源码已经被作者用c++11重写,我学习的版本是没有使用c++11版本的。不过二者大同小异,核心思想是没有变化的。点这里可以看我的源代码,如果你对我之前的博客有兴趣,可以点击下面的连接:
muduo网络库源码复现笔记(一):base库的Timestamp.h
muduo网络库源码复现笔记(二):base库的Atomic.h
muduo网络库源码复现笔记(三):base库的Exception.h
muduo网络库源码复现笔记(四):base库的Thread.h和CurrentThread.h
muduo网络库源码复现笔记(五):base库的Mutex.h和Condition.h和CoutntDownLatch.h
muduo网络库源码复现笔记(六):base库的BlockingQueue.h和BoundedBlockingQueue.h
muduo网络库源码复现笔记(七):base库的ThreadPool.h
muduo网络库源码复现笔记(八):base库的Singleton.h
muduo网络库源码复现笔记(九):base库的ThreadLocalSingleton.h
muduo网络库源码复现笔记(十):base库的ThreadLocalSingleton.h
muduo网络库源码复现笔记(十一):base库的StringPiece.h

LogStream.h

LogStream.h主要封装了两个类,分别是FixedBuffer和LogStream。muduo库在打印日志的时候,由Logger(后面再讲)调用一个LogStream将要打印的信息输入到LogStream的缓冲区FixedBuffer,接着再输出到标准输出和文件中。
接下来看看这两个是如何封装的。

FixedBuffer

正如前所述,FixBuffer是LogStream的缓冲区,它也是个类模板,SIZE可以指定缓冲区大小。看一下它的代码:

template<int SIZE>
class FixedBuffer : boost::noncopyable
{
public:
	FixedBuffer()
		: cur_(data_)
	{
		setCookie(cookieStart);
	}	

	~FixedBuffer()
	{
		setCookie(cookieEnd);
	}

	void append(const char* buf,size_t len)
	{
		if(implicit_cast<size_t>(avail()) > len)
		{	
			memcpy(cur_,buf,len);		
			cur_ += len;
		}
	}

	const char* data() const {return data_;}
	int length() const {return static_cast<int>(cur_ - data_);}
	char* current() {return cur_;}
	int avail() const {return static_cast<int>(end() - cur_);}
	void add(size_t len) {cur_ += len;}
	void reset() {cur_ = data_;}
	void bzero() {::bzero(data_,sizeof data_);}
	//used by gdb
	const char* debugString();
	void setCookie(void (*cookie)()) {cookie_ = cookie;}
	//used by unit test
	string asString() const {return string(data_,length());}

private:
	const char* end() const {return data_ + sizeof data_;}
	static void cookieStart();
	static void cookieEnd();

	void (*cookie_)();
	char data_[SIZE];
	char* cur_;
};

FixedBuffer的私有成员

私有成员值得关注的是cur_和data_。data_用于缓存数据,而cur_是指向data_最后一位写入数据下一个字节的指针。FixedBuffer的成员函数大多是围绕这两个私有成员的运算。

append函数

append函数是有两个参数,data是待加入缓冲区的数据,len是数据长度。首先检查一下缓冲区剩余空间是否大于data的长度,若是,进行内存拷贝,移动指针。剩下的函数就没有什么好说的了,都不是很难。

LogStream类

LogStream类如前所述,是将要输出的信息加载到FixedBuffer缓冲区,等待Logger(后面讲)进一步处理。LoggerStream需要重点关注的地方是<<操作符重载。我们将要实现的效果是将任意类型的数据通过运算符<<加载到缓冲区。

<<操作符加载字符类

字符类加载比较简单,利用好FixedBuffer的append函数即可。如果我们有LogStream os,可以使用os << 数据;将任意字符类型加载到缓冲区,包括char、char[]、string、StringPiece。

	//typedef Logstream self has defined
	self& operator<<(char v)
	{
		buffer_.append(&v,1);
		return *this;
	}
	
	self& operator<<(const char* v)
	{
		buffer_.append(v,strlen(v));
		return *this;
	}

	self& operator<<(const string& v)
	{
		buffer_.append(v.c_str(),v.size());
		return *this;
	}
#ifndef MUDUO_STD_STRING
	self& operator<<(const std::string& v)
	{
		buffer_.append(v.c_str(),v.size());
		return *this;
	}
#endif
	self& operator<<(const StringPiece& v)
	{
		buffer_.append(v.data(),v.size());
		return *this;
	}

<<操作符加载整数到缓冲区

如果我们要将一个整数数据加载到缓冲区,势必要把这个数据转换成字符数据。这里的整数数据包括short、unsigned short、int、unsigned int、long、unsigned long、long long、unsigned long long。muduo中实现的转换函数使用了Mattew Wilson的整数转换函数。

const char digits[] = "9876543210123456789";
const char* zero = digits + 9;
template<typename T>
size_t convert(char buf[],T value)
{
	T i = value;
	char* p = buf;

	do
	{
		int lsd = static_cast<int>(i % 10);
		i /= 10;
		*p++ = zero[lsd]; //zhen ni ma niu p
	}while(i != 0);

	if(value < 0)
	{
		*p++ = '-';
	}
	*p = '\0';
	std::reverse(buf,p);
	return p - buf;
}

这个函数是一个高效整数转字符串函数。它的优点在于通用与正数和负数。我也是通过学习这个函数第一次了解数组还能使用负数下标,是一个非常值得回味的函数。
有了转换函数后,可以将任意整数数据转换为字符串加载到缓冲区了,在操作符重载函数中,调用formatInteger函数即可。

template<typename T>
void LogStream::formatInteger(T v)
{
	if(buffer_.avail() > kMaxNumericSize)
	{
		size_t len = convert(buffer_.current(),v);
		buffer_.add(len);
	}	
}

举个例子

LogStream& LogStream::operator<<(long long v)
{
	formatInteger(v);
	return *this;
}

<<操作符加载浮点数到缓冲区

加载浮点数没有什么好的方法,使用snprintf函数即可。

LogStream& LogStream::operator<< (double v)
{
	if(buffer_.avail() > kMaxNumericSize)
	{
		int len = snprintf(buffer_.current(),kMaxNumericSize,"%.12g",v);
		buffer_.add(len);
	}
	return *this;	
}

或许可以使用Grisu3算法?找时间试试。

总结

封装好这个类之后,对于任意数据,总是可以使用LogStream os;os << 数据的方式加载数据到字符缓冲区待处理。学习完这部分知识后感觉字符部分的知识增加了不少。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值