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 << 数据的方式加载数据到字符缓冲区待处理。学习完这部分知识后感觉字符部分的知识增加了不少。