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
muduo网络库源码复现笔记(十二):base库的LogStream.h
muduo网络库源码复现笔记(十三):base库的Logging.h
这个文件封装了SmallFile类和函数readFile协助LogFile类(后面讲)实现滚动日志。下面分别分析一下readFile函数和SmallFile类。
readFile函数代码不难,它有5个参数。第一个参数StringPiece,代表待读取的文件路径,用于初始化SmallFile类;第二个参数是读取的最大字节数;第三个参数是一个字符串,用于存储读取内容,虽然它的类型是String*,但在具体使用时,将特化为std::string或string,其余两个参数是读取文件最后一次修改时间和创建时间。
readFile创建一个SmallFile类后调用这个类的readToString函数。下面我们讲解SmallFile类。
template
int readFile(StringPiece filename,int maxSize,String* content,int64_t* fileSize = NULL,int64_t* modifyTime = NULL,
int64_t* createTime = NULL)
{
SmallFile file(filename);
return file.readToString(maxSize,content,fileSize,modifyTime,createTime);
}
SmallFile类的代码如下,它的私有变量为fd_(待读取文件的文件描述符,在构造函数中获取),err_(读取文件时出错的错误代码),buf_(缓冲区)。
成员函数中比较值得关注的是readToString和readBuffer,下面分析一下代码。
class SmallFile : boost::noncopyable
{
public:
SmallFile(StringPiece filename);
~SmallFile();
template
int readToString(int maxSize,String* content,int64_t* fileSize,
int64_t* modifyTime,int64_t* createTime);
int readToBuffer(int* size);
const char* buffer() const {return buf_;}
static const int kBufferSize = 65536;
private:
int fd_;
int err_;
char buf_[kBufferSize];
};
readToString函数代码很长,逻辑却很清晰,作用是将待读取文件读入字符串content。如果文件被正确打开,便使用fstat函数将fd_指向文件的文件信息存储在statbuf中。stabuf是一个结构体,其成员如下所示
struct stat {
mode_t st_mode; //文件对应的模式,文件,目录等
ino_t st_ino; //inode节点号
dev_t st_dev; //设备号码
dev_t st_rdev; //特殊设备号码
nlink_t st_nlink; //文件的连接数
uid_t st_uid; //文件所有者
gid_t st_gid; //文件所有者对应的组
off_t st_size; //普通文件,对应的文件字节数
time_t st_atime; //文件最后被访问的时间
time_t st_mtime; //文件内容最后被修改的时间
time_t st_ctime; //文件状态改变时间
blksize_t st_blksize; //文件内容对应的块大小
blkcnt_t st_blocks; //伟建内容对应的块数量
};
而S_ISREG是一个宏,用于检测st_mode是否是一个常规文件,同理S_ISDIR检测是否是文件夹。若是常规文件,为字符串预先保留空间。
进入while循环后,读取字符进入content,其中buf_用于协助读取字符,防止溢出。
template
int FileUtil::SmallFile::readToString(int maxSize,String* content,
int64_t* fileSize,int64_t* modifyTime,
int64_t* createTime)
{
BOOST_STATIC_ASSERT(sizeof(off_t) == 8);
assert(content != NULL);
int err = err_;
if(fd_ > 0)
{
content -> clear();
if(fileSize)
{
struct stat statbuf;
if(::fstat(fd_,&statbuf) == 0)
{
if(S_ISREG(statbuf.st_mode))
{
*fileSize = statbuf.st_size;
content -> reserve(static_cast
(std::min(implicit_cast(maxSize),*fileSize)));
}
else if(S_ISDIR(statbuf.st_mode))
{
err = EISDIR;
}
if(modifyTime)
{
*modifyTime = statbuf.st_mtime;
}
if(createTime)
{
*createTime = statbuf.st_ctime;
}
}
else
{
err = errno;
}
}
while(content->size() < implicit_cast(maxSize))
{
size_t toRead = std::min(implicit_cast(maxSize) - content->size(),sizeof(buf_));
ssize_t n = ::read(fd_,buf_,toRead);
if(n > 0)
{
content->append(buf_,n);
}
else
{
if(n < 0)
{
err = errno;
}
break;
}
}
}
return err;
}
相比readToString函数,这个函数不难。注意pread是带偏移量的原子读取函数。
int FileUtil::SmallFile::readToBuffer(int* size)
{
int err = err_;
if(fd_ >= 0)
{
ssize_t n = ::pread(fd_,buf_,sizeof(buf_) - 1,0);
if(n >= 0)
{
if(size)
{
*size = static_cast(n);
}
buf_[n] = '\0';
}
else
{
err = errno;
}
}
return err;
}