muduo网络库源码复现笔记(十六):base库的LogFile.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
muduo网络库源码复现笔记(十二):base库的LogStream.h
muduo网络库源码复现笔记(十三):base库的Logging.h
muduo网络库源码复现笔记(十四):base库的FileUtil.h
muduo网络库源码复现笔记(十五):base库的ProcessInfo.h

1 LogFile.h

LogFile.h头文件中的LogFile类实现的是滚动日志的功能。所谓滚动日志,它具有两个功能:(1)当日志大小达到一定数目时如1G,自动开启一个新日志;(2)当到达每天零点时,开启一个新日志。同时我们对日志的命名也有明确的规范,是按运行程序名称.年-月-日-时-分-秒-微秒.主机名称.log的格式来命名的。下面我们来看看具体的实现过程。

1.1 File类

LogFile类能实现滚动日志要借助于File类。Logfile类有个成员boost::scoped_ptr file_(scoped_ptr就是c++11里的unique_ptr智能指针),当我们要写入新日志时只需要new出一个File类初始化它。下面分析一些File的关键函数。

class LogFile::File : boost::noncopyable
{
public:
	explicit File(const string& filename)
		:	fp_(::fopen(filename.data(),"ae")),
			writtenBytes_(0)
	{
		assert(fp_);
		::setbuffer(fp_,buffer_,sizeof buffer_);	
	}

	~File()
	{
		::fclose(fp_);
	}

	void append(const char* logline,const size_t len)
	{
		size_t n = write(logline,len);
		size_t remain = len - n;
		while(remain > 0)
		{
			size_t x = write(logline+n,remain);
			if(x == 0)
			{
				int err = ferror(fp_);
				if(err)
				{
					fprintf(stderr,"LogFile::FIle::append() failed %s\n",
							strerror_tl(err));
				}
				break;
			}
			n += x;
			remain = len - n;
		}
		writtenBytes_ += len;	
	}

	void flush()
	{
		::fflush(fp_);
	}
	
	size_t writtenBytes() const {return writtenBytes_;}
private:
	size_t write(const char* logline,size_t len)
	{
#undef fwrite_unlocked
		return ::fwrite_unlocked(logline,1,len,fp_);
	}

	FILE* fp_;
	char buffer_[64*1024];
	size_t writtenBytes_;
};

1.1 File类的构造与析构函数

在构造函数中,使用fopen打开文件,用返回的文件指针初始化fp_;writtenBytes_是这个文件已经写入的字节数,初始化为0;在构造函数的逻辑中,将文件缓冲区设置为buffer_,buffer_大小是64k。
析构函数中关闭文件指针即可。

1.2File类的append函数

append函数用于向文件写入细节。若写入字节后remain > 0,接着在while中写入。注意append中使用的write函数不是fcntl.h的write函数,而是File类中封装的write函数。这里的write函数封装了fwrite_locked,不使用锁也不判断其他函数是否使用锁,所以不是线程安全的,当然如果有线程安全考虑的话也可以在外部调用它时加锁。

2 LogFile类

接下来进入正题,看LogFile类如何实现滚动日志。LogFile的私有成员变量中
basename_是指运行程序的basename,rollSize_是指字节数达到多少时开启一个新日志,flushInterval_是flush间隔。startPeriod_是指一个日志开启的当天0点的时刻(距离UTC 1970-1-1-0:00的秒数),lastRoll是上次开启新日志的时刻,lastFlush是上次flush的时刻。这些变量在构造函数中初始化。还有两个常量kCheckTimeRoll_与count_配合检查是否达到第二天,后面在append_unlocked函数中讲。kRollPerSeconds_就是一天的秒数。

lass LogFile : boost::noncopyable
{
public:
	LogFile(const string& basename,size_t rollSize,
				bool threadSafe = true,int flushInterval = 3);
	~LogFile();
	
	void append(const char* logline,int len);
	void flush();

private:
	void append_unlocked(const char* logline,int len);
	
	static string getLogFileName(const string& basename,time_t* now);
	void rollFile();

	const string basename_;//basename of logfile;
	const size_t rollSize_; //maxsize of a roll logfile
	const int flushInterval_; //

	int count_;

	boost::scoped_ptr<MutexLock> mutex_;
	time_t startOfPeriod_; 
	time_t lastRoll_;
	time_t lastFlush_;
	class File;
	boost::scoped_ptr<File> file_;

	const static int kCheckTimeRoll_ = 1024;
	const static int kRollPerSeconds_ = 60 * 60 * 24;
};

2.1 LogFile构造函数

LogFile函数的构造函数如下,计数count_被初试化为0, startOfPeriod_、lastRoll_、lastFlush_同样都是0。初始化完毕,使用rollFile()函数开启一个新日志。

LogFile::LogFile(const string& basename,size_t rollSize,
					bool threadSafe,int flushInterval)
	:	basename_(basename),rollSize_(rollSize),
		flushInterval_(flushInterval),
		count_(0),
		mutex_(threadSafe ? new MutexLock : NULL),
		startOfPeriod_(0),
		lastRoll_(0),
		lastFlush_(0)
{
	assert(basename.find('/') == string::npos);
	rollFile();	
}

2.2 rollFile函数–开启新日志

在rollFile中,首先使用getLogFileName函数(代码忽略,不难,我的github有)获取日志的规范名称(前面提到过),并且讲now置为现在的时刻。注意start的赋值,它将now进行了取整,得到的结果就是now当天的零点时刻。随后进行赋值与file_指针初始化。rollFile函数除了在构造函数中使用,还在append函数中使用以实现滚动功能

void LogFile::rollFile()
{
	time_t now = 0;
	string filename = getLogFileName(basename_,&now);
	time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;
	
	if(now > lastRoll_)
	{
		lastRoll_ = now;
		lastFlush_ = now;
		startOfPeriod_ = start;
		file_.reset(new File(filename));
	}
}

2.3 append函数–公开字节写入函数

append函数是公开以被调用向日志写入字节。append函数有两个参数,一个是要写入的内容,一个是内容长度。根据是否线程安全的考虑来决定是否加锁,而真正进行字节写入的函数是私有的append_unlocked;

void LogFile::append(const char* logline,int len)
{
	if(mutex_)
	{
		MutexLockGuard lock(*mutex_);
		append_unlocked(logline,len);
	}
	else
	{
		append_unlocked(logline,len);
	}
}

2.4 append_unlocked–实现日志滚动

append_unlocked是一个私有函数,它的函数参数和公有的append一样,这个函数实现的前面提到的日志滚动的效果。如果我们要向日志中写入字节,直接调用file_的append即可。写完之后检查目前日志的字节数是否大于rollSize_,若是,开启新日志,这样实现了滚动日志的第一个功能要求。接下来检查计数器count_是否大于kChekTimeRoll_。只有count_ > kCheckTimeRoll_时,才会检查目前是否到了第二天。检查是否到第二天的方法依旧是将当前时刻取整与startPeriod比较,这样就实现了第二个功能。

void LogFile::append_unlocked(const char* logline,int len)
{
	file_ -> append(logline,len);
	
	if(file_->writtenBytes() > rollSize_)
	{
		rollFile();
	}
	else
	{
		if(count_ > kCheckTimeRoll_)
		{
			count_ = 0;
			time_t now = ::time(NULL);
			time_t thisPeriod_ = now / kRollPerSeconds_ * kRollPerSeconds_;
			if(thisPeriod_ != startOfPeriod_)
			{
				rollFile();
			}
			else if(now - lastFlush_ > flushInterval_)
			{
				lastFlush_ = now;
				file_ -> flush();
			}
		}
		else
		{
			++count_;
		}	
	}
}

2.5 使用

我们可以使用Logging定义的宏来实现日志输入。如

boost::scoped_ptr<muduo::LogFile> g_logfile;

void outputFunc(const char* msg,int len)
{
	g_logfile -> append(msg,len);
} 
g_logfile.reset(new muduo::LogFile(::basename(filename),150 * 1024));//日志容量上限150k
	muduo::Logger::setOutput(outputFunc);//设定Logger的输出逻辑。

在主函数中可以使用LOG*来向日志输入字节信息了。

LOG_INFO<<"messages";
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Linux多线程服务端编程:使用muduo C++网络》是一本介绍使用muduo C++网络进行多线程服务端编程的电子书。该书由陈硕所著,适用于想要学习Linux多线程网络编程的开发人员。 本书从基础概念开始,详细介绍了多线程网络编程的原理和技术,并结合muduo C++网络的使用示例,演示了如何开发高性能、稳定的网络服务端程序。 muduo C++网络是一个基于事件驱动的网络编程,它采用了Reactor模式,提供了高效的异步IO操作。该封装了多线程、多进程、事件等相关操作,使得开发者可以简单、快速地开发网络服务端应用。 在本书中,作者通过具体的实例和代码示例,讲解了muduo C++网络的使用方法和注意事项。书中内容分为多个章节,包括网络编程基础、IO复用、事件回调、线程同步、线程池等,涵盖了开发者在实际项目中可能遇到的各种情况。 通过学习《Linux多线程服务端编程:使用muduo C++网络》,读者可以了解到多线程服务端编程的基本原理和技术,掌握使用muduo C++网络进行高效开发的方法,并能够开发出高并发、高性能的网络服务端应用。 总之,该书是一本实用的网络编程指南,对于想要学习Linux多线程网络编程以及使用muduo C++网络的开发人员来说,具有较高的参考价值。 ### 回答2: 《Linux 多线程服务端编程:使用 muduo C++ 网络》是一本介绍如何使用 muduo C++ 网络进行 Linux 多线程服务端编程的指南。该书主要目的是教读者如何构建高性能、可扩展的网络服务端应用程序。 该书首先介绍了多线程编程的基础知识,包括线程创建、线程同步与互斥、线程安全的数据结构等内容。然后,书中详细介绍了 muduo C++ 网络的使用方法,包括网络编程基础、事件驱动模型、网络编程的设计模式等。读者可以通过学习这些内容,了解如何使用 muduo C++ 网络来构建高性能的多线程服务端。 该书还介绍了业界常用的网络协议及其实现原理,例如 TCP/IP、HTTP 协议等。通过学习这些知识,读者可以更好地理解网络编程的工作原理,从而更好地设计和实现自己的网络服务端应用程序。 此外,书中还涵盖了一些实际案例和实战经验,读者可以通过这些案例了解如何应对常见的网络编程问题,并且学习到一些实际的开发技巧和调试技巧。 总而言之,《Linux 多线程服务端编程:使用 muduo C++ 网络》是一本非常实用的指南,可以帮助读者快速入门多线程服务端编程,并且掌握使用 muduo C++ 网络构建高性能的网络服务端应用程序的技巧。无论是初学者还是有一定网络编程经验的开发者,都可以从这本书中获得很多有价值的知识和经验。 ### 回答3: 《Linux 多线程服务端编程:使用 muduo C++ 网络》是一本关于使用muduo C++网络进行Linux多线程服务端编程的书籍。本书以muduo C++网络为基础,深入讲解了多线程服务端编程的相关知识和技巧。 本书主要内容包括: 1. muduo的介绍:介绍了muduo的特性、设计思想和基本用法。muduo是基于Reactor模式的网络,提供了高效的事件驱动网络编程框架,有助于开发者快速搭建高性能的网络服务端。 2. 多线程编程的基础知识:介绍了多线程编程的基本概念和相关的线程同步和互斥机制,如互斥锁、条件变量等。并讲解了如何正确地使用这些机制,以保证多线程程序的正确性和高效性。 3. muduo C++网络的使用:详细介绍了muduo的线程模型、事件驱动机制和网络编程接口。通过实例代码和示意图,演示了如何使用muduo构建一个多线程的网络服务端,包括创建监听套接字、事件的注册和处理、多线程任务分配等。 4. 高性能服务端的设计和优化:讲解了如何设计和优化高性能的多线程服务端。包括使用线程池提高并发处理能力、使用非阻塞IO提升数据处理效率、优化网络通信性能等方面的内容。 该书适合具有一定Linux编程基础的开发人员学习和参考。通过学习该书,读者可以掌握使用muduo C++网络进行多线程服务端编程的技巧,提升服务端的性能和可靠性。同时,也可了解到网络编程领域的一些高级技术和最佳实践。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值