前言
学习c++的过程中,伴随着迷茫,不知道如何使用特性,不知道如何将函数分类,不知道如何写代码和注释符合规范,以及软件工程中的UML绘图,功能如何拆分,系统调用的使用,工程是如何写出来的。在无人领路去自学这些的情况下,无疑是痛苦的。古人云,学而不思则罔,思而不学则殆。这些文章只是记录我所学的和我所思的,可能观点和方法会是错的,但闭门造车是更大的错误。希望能将我的学和思分享给大家,大家再根据自己的认识相互的交流,进一步的相互学习。在此过程中也能记录自己走过的路以慰未来的我。我现在会往Linux服务器编程的方向上去努力,现阶段目标暂定跟写
sylar,这个项目在b站上有视频,github上也有代码(虽然好像没有日志部分)但是能跟着别人的思路去写,有大佬告诉为什么这么写那就是已经是非常美好的事情了。
这是这位大佬项目的github地址
github: https://github.com/sylar-yin/sylar
我自己跟写的完整日志器代码整理地址
https://blog.csdn.net/m0_55292629/article/details/125262528
一、安装环境
环境是随时间随想做的方向不断变化的,虽然视频中所说的环境使用的是centos,但是我还是暂时使用的ubuntu20.04-servers-amd64版本。(原因centos可能过段时间会停止维护,而使用服务器版本的原因是使用桌面版放久了容易卡死不动),我所下载地址:
中科大源 :http://mirrors.ustc.edu.cn/ubuntu-releases
预先善其事,必先利其器
初级阶段暂时只要安装以下环境
gcc 和 cmake 安装
sudo apt-get install cmake
sudo apt install build-essential
若是没有网的话,可以使用命令配置网络信息
sudo vi /etc/network/interfaces
方法一
至于编程IDE可以选择选择使用visual studio 2019远程编程参考文章如下
https://blog.csdn.net/qq_41683305/article/details/122386148
其中碰到了无法导入linux内系统库的问题直接导致了无法代码补全,包含头文件报错。
具体的解决思路是将linux本地的包
/user/include
/usr/local/include
直接打包然后再复制到项目文件夹内。再从本地调用思路类似于
https://blog.csdn.net/qq_34950682/article/details/107323887?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-107323887-blog-116646073.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-107323887-blog-116646073.pc_relevant_default&utm_relevant_index=2
在此操作不同的是可以使用filezilla软件直接找到前面说的文件夹将内容复制到想要的位置。
具体过程为在visual studio内
项目->属性->C/C+±>常规
虽然我是将这些包复制到了项目内,但是这里我觉得可以专门开辟一个单独的文件夹用来存放和同步linux库头文件。可以用来防止有多个linux项目时需要备份多次的情况。
方法二
或者选择使用vim,上面提到的项目好像使用的就是经过拓张的vim,对此也进行了研究,一般使用的插件为YouCompleteMe。
前提需要git cmke Vundle python3等具体的安装可以直接去github上面找,下面提供github地址和插件的安装教程
https://github.com/ycm-core/YouCompleteMe
但是ubuntu好像只需要两行代码配置
sudo apt install vim-youcompleteme
vim-addons install youcompleteme
其中可能会碰到"NoExtraConfDetected"问题可以参考下面这篇文章
https://blog.csdn.net/u014070086/article/details/88692896
方法三
vscode 远程连接服务器方式
https://blog.csdn.net/mikudouble/article/details/120071377
其中还需要增加一个terminal插件,对于c/c++的插件也可以换成clangd插件(clangd插件好使一些不管是打开速度还是代码补全方面都好一些,c/c++总是容易卡)但是不要两个插件同时开,就可以直接在windows平台上操作Linux服务器,论实现来说,这个vscode最省心省力,代码补全啥的也都有,因此本人使用的是vscode搭配插件的方式来进行跟写的。
二、正式跟写
1.日志部分
日志部分最主要的类是日志器,事件器,和输出器,并且是以一个namespace来进行的描述的,所有日志有关的类写在了一个文件里(log.h,log.cc),里面大量使用了shared_ptr指针,大致关系结构如下
其中ptr是shared_ptr的智能指针
其中分工大致为
- LogEvent是存放信息的地方,包括行、列、线程号、协程号、内容等
- LogFormatter是安排数据摆放顺序,包含模式解析等功能(可以看看log4j标准)
- LogAppender是输出地,可以选择文件或控制台进行输出,里面也包含了LogFormatter对象,来保证不同的输出地可以以不同的格式呈现
- Logger是日志器,作为控制中枢可以将同一个信息添加多个LogAppender和LogFormatter对象来保证可以同时往多个地方输出
LogEvent
class LogEvent {
public:
typedef std::shared_ptr<LogEvent> ptr;
LogEvent(std::shared_ptr<Logger> logger,LogLevel::Level level,const char *file, int32_t line, uint32_t elapse, uint32_t thread_id, uint32_t fiber_id, uint64_t time);
const char *getFile() const {
return m_file; }
int32_t getLine() const {
return m_line; }
uint32_t getElapse() const {
return m_elapse; }
uint32_t getThreadId() const {
return m_threadId; }
uint32_t getFiberId() const {
return m_fiberId; }
uint64_t getTime() const {
return m_time; }
std::string getContent() const {
return m_ss.str(); }
std::stringstream &getSS() {
return m_ss; }
std::shared_ptr<Logger> getLogger()const{
return m_logger;}
LogLevel::Level getLevel() const {
return m_level; }
void format(const char *fmt, ...);
void format(const char *fmt,va_list al);
private:
const char *m_file = nullptr; //文件名
int32_t m_line = 0; // 行号
uint32_t m_elapse = 0; //程序启动开始到现在的毫秒数
uint32_t m_threadId = 0; //线程id
uint32_t m_fiberId = 0; //协程
uint32_t m_time = 0; //时间戳
std::stringstream m_ss;
std::shared_ptr<Logger> m_logger;
LogLevel::Level m_level;
};
void LogEvent::format(const char *fmt, ...) {
//经典C字符串处理方式
va_list al;
va_start(al, fmt);
format(fmt, al);
va_end(al);
}
void LogEvent::format(const char *fmt, va_list al) {
char *buf = nullptr;
int len = vasprintf(&buf, fmt, al);
if (len != -1) {
m_ss << std::string(buf, len);
free(buf);
}
}
LogEvent::LogEvent(std::shared_ptr<Logger> logger, LogLevel::Level level,
const char *file, int32_t line, uint32_t elapse,
uint32_t thread_id, uint32_t fiber_id, uint64_t time)
: m_file(file), m_line(line), m_elapse(elapse), m_threadId(thread_id),
m_fiberId(fiber_id), m_time(time),m_logger(logger),m_level(level)
{
}
此类相当于一个日志消息的数据库,函数也主要是获得类内的信息,并且保存起来。
sylar::LogEvent::ptr(new sylar::LogEvent(
logger, level, __FILE__, __LINE__, 0, sylar::GetThreadId(),
sylar::GetFiberId(), time(0))))
//使用智能指针进行初始化,也是信息的初始化
//控制器、行号、信息等级、文件名、行号、获取线程号、协程号、已运行时间,可以通过各种函数和宏去实现
LogFormatter类主要是根据pattern字符串安排各个信息排列的顺序以及附加其他的信息(此处可以看log4j格式)。信息是拼接起来的,其中使用了字符串流进行拼接。解析pattern字符串后按照解析顺序将信息存放到有序数组里。使用的时候将这些信息一一打印出来。
class LogFormatter{
public:
typedef std::shared_ptr<LogFormatter> ptr;
LogFormatter(const std::string &pattern);
std::string format(std