首页 > 其他分享 >[分布式网络通讯框架]----MprpcController以及Logger类

[分布式网络通讯框架]----MprpcController以及Logger类

时间:2024-07-04 12:56:10浏览次数:17  
标签:std logger 队列 网络通讯 ---- MprpcController 线程 日志 Logger

在calluserservice.cc中,使用UserServiceRpc_Stub类的时候,我们最终调用形式为:stub.Login(&controller,&request,&response,nullptr);
注意到其中有一个controller对象,这个是由MprpcController类定义出来的对象,那么这个类的作用是什么呢?

  • 首先我们来看 Login() 的底层实现,传入的controller到底是一个什么。
    在这里插入图片描述
  • 可以看到,controller实际上是RpcController* 类;
    *
  • RpcController* 类实际上是一个抽象类,底层封装了各类纯虚函数,我们通过继承这个类,并且重写对应的函数,来判断rpc的调用是否成功。
  • 如果不判断是否调用成功就直接读取response ,是假设request成功的,在其中不会发生任何的错误,但是这种情况是理想化的,在其中会出现很多问题。如:网络建立连接错误 各种地方的return exit等 都会造成没有response响应。

MprpcController类

class MprpcController:public google::protobuf::RpcController
{
    省略...........省略
};
  • 很明确 它是继承了 google::protobuf::RpcController类。

重要成员变量

bool m_failed; 
  • 记录rpc方法执行过程中的状态
std::string m_errText;
  • 记录rpc方法执行过程中的错误信息

重要成员函数

构造函数

MprpcController::MprpcController()
{
    m_failed = false;
    m_errText = "";
}
  • 初始化成员变量

void Reset();

void MprpcController::Reset()
{
    m_failed = false;
    m_errText = "";
}
  • 重置成员变量的值

bool Failed() const;

bool MprpcController::Failed() const
{
    return m_failed;
}
  • 返回rpc方法执行过程中的状态,如果是false,我们将不会读取response值。

std::string ErrorText() const;

std::string MprpcController::ErrorText() const
{
    return m_errText;
}
  • 返回rpc方法执行过程中的错误信息。

void SetFailed(const std::string& reason);

void MprpcController::SetFailed(const std::string &reason)
{
    m_failed = true;
    m_errText = reason;
}
  • 在我们调用的过程中,通过该函数,写错误原因。

例如

if(rpcHeader.SerializeToString(&rpc_header_str))
{
   header_size=rpc_header_str.size();
}
else
{
   controller->SetFailed("Serialize rpc header error!");
   return;
}

整个项目的主体部分,就到此结束了,剩余一个logger类,这也是我们在做大型项目的必备类,通过日志,可以简单明了的帮我们分析到程序的问题所在,这里采用了异步,同时有多个worker线程都会向日志queue队列中写日志,而只有一个线程读日志queue,向指定文件中写日志文件。

Logger类

为什么需要异步记录日志

因为基于muduo网络库进行网络通讯的,muduo通过多线程来处理并发连接,要添加日志模块那么就会有多个线程写日志信息的情况。这样的话就必须要实现一个保证线程安全的日志队列。所以需要启动一个日志线程,专门对日志队列写日志。

保证线程安全的日志队列类

为了保证线程安全,项目中提供了模板类 lockqueue template<typename T>,它用于实现异步写日志的日志队列,主要包含 push 和 pop 两个方法。

重要成员变量

std::queue<T> m_queue;
std::mutex m_mutex;
std::condition_variable m_condvariable;
  • 队列
  • 条件变量

重要成员函数

void Push(const T &data)
void Push(const T &data)
{
   std::lock_guard<std::mutex> lock(m_mutex);
   m_queue.push(data);
   m_condvariable.notify_one();
}
  • push 方法可以被多个 worker 线程调用以将数据添加到日志队列中
T Pop()
T Pop()
{
   std::unique_lock<std::mutex> lock(m_mutex);

   while(m_queue.empty())
   {
       //日志队列为空,线程进入wait状态,并且释放锁
       m_condvariable.wait(lock);
   }

   T data=m_queue.front();
   m_queue.pop();
   return data;
}
  • pop 方法则只能由一个线程读取队列并将其内容写入日志文件。

实际上,各个线程通过push 方法使用了 std::lock_guardstd::mutex进行加锁,然后将数据添加到队列中,最后通过条件变量std::condition_variable唤醒 pop 方法所在的线程。pop 方法获得锁后,然后进入一个 while 循环,在循环中检查队列是否为空,如果为空,则调用条件变量的 wait 方法使当前线程阻塞等待日志的产生。当队列不为空时,将队头元素取出,并从队列中删除。最后释放锁并返回取出的队头元素。

优点:通过这种方式实现日志队列的异步操作,可以让写日志的线程和写文件的线程分别跑在不同的线程中,避免了日志写操作对主程序的性能影响。

Logger类

日志类属于是单例模式,确保了整个应用程序中只有一个logger实例。

重要成员变量

enum LogLevel //日志级别
{
    INFO,//普通信息
    ERROR,//错误信息
};

int m_loglevel;//记录日志级别

LockQueue<std::string> m_lckQue;//日志缓冲队列

重要成员函数

Logger()
Logger::Logger()
{
    //启动专门的写日志线程
    std::thread writeLogTask([&](){
        for(;;)
        {
            //获取当天的日期,然后取日志信息,写入相应的日志文件当中 a+
            time_t now=time(nullptr);
            tm *nowtm = localtime(&now);

            char file_name[128];
            sprintf(file_name,"%d-%d-%d-log.txt",nowtm->tm_year+1900
                    ,nowtm->tm_mon+1,nowtm->tm_mday);
            
            FILE *pf = fopen(file_name,"a+");
            if(pf==nullptr)
            {
                std::cout<<"logger file: "<<file_name<<" open error!"
                    <<std::endl;
                exit(EXIT_FAILURE);
            }

            std::string msg=m_lckQue.Pop();

            char time_buf[128]={0};
            sprintf(time_buf,"%d:%d:%d=> [%s] "
                    ,nowtm->tm_hour
                    ,nowtm->tm_min
                    ,nowtm->tm_sec
                    ,(m_loglevel==INFO?"INFO":"ERROR"));
            msg.insert(0,time_buf);
            msg.append("\n");

            fputs(msg.c_str(),pf);
            fclose(pf);
        }
    });
    //设置分离线程,守护线程
    writeLogTask.detach();
}

  • 在logger的构造函数中,发起了一个线程writelogtask,该线程循环执行以下操作, 该线程会一直运行,为整个应用程序提供日志服务;
  1. 调用系统localtime函数获取当前时间,尝试打开当日的日志文件
  2. 调用lockqueue类的pop()函数,从lockqueue中获取缓存的日志信息;
  3. 获取时分秒时间,以及根据日志级别,添加日志级别前缀,并将该条日志写入日志文件中
  • 设置分离线程,守护线程
static Logger& GetInstance();
Logger &Logger::GetInstance()
{
    static Logger logger;
    return logger;
}
  • 获取唯一单例对象
void SetLogLevel(LogLevel level);
void Logger::SetLogLevel(LogLevel level)
{
    m_loglevel=level;
}
  • 设置日志级别
void Log(std::string msg);
void Logger::Log(std::string msg)
{
    m_lckQue.Push(msg);
}
  • 把日志信息写入Lockqueue缓冲区当中

和muduo网络库中的实现类似,本项目也提供了日志的宏,它接受一个格式化的日志消息和可变数量的参数。并为了避免展开时出错,我们采用了do-while(0)语法在实际使用过程中,log_info(“xxx %d %s”, 20, “xxxx”) 可以被展开。

#define LOG_INFO(logmsgformat, ...)\
    do\
    {\
        Logger &logger =Logger::GetInstance();\
        logger.SetLogLevel(INFO);\
        char c[1024]={0};\
        snprintf(c,1024,logmsgformat,##__VA_ARGS__);\
        logger.Log(c);\
    }while (0);

#define LOG_ERROR(logmsgformat, ...)\
    do\
    {\
        Logger &logger =Logger::GetInstance();\
        logger.SetLogLevel(ERROR);\
        char c[1024]={0};\
        snprintf(c,1024,logmsgformat,##__VA_ARGS__);\
        logger.Log(c);\
    }while (0);
  • 在宏内部,获取logger的实例
  • 设置日志级别为info;
  • 创建一个长度为1024的char数组c,使用snprintf函数将格式化字符串(logmsgformat) 和可变参数(va_args)写入这个数组中;
  • 调用logger的log函数将日志消息写入日志文件中。

标签:std,logger,队列,网络通讯,----,MprpcController,线程,日志,Logger
From: https://blog.csdn.net/m0_73537205/article/details/139932991

相关文章

  • Go语言--流程控制
    程序运行结构Go语言支持最基本的三种程序运行结构:顺序结构、选结构、环结构。顺序结构:程序按顺序执行,不发生跳转。选择结构:依据是否满足条件,有选择的执行相应功能循环结构:依据条件是否满足,环多次执行某段代码。选择ifs:="yes"ifs=="yes"{ fmt.Println("YE......
  • 2024Faceboo 商城自然流(从入门到精通),玩转脸书商城全闭环(教程+资料)
    摘要:本文旨在为读者提供一个全面的Facebook商城操作指南,从基础知识到高级应用技巧,帮助用户深入理解并有效利用Facebook商城进行跨境电商活动。1.引言介绍Facebook商城的发展历程及其在全球电商领域的影响力。2.Facebook商城概述2.1Facebook平台简介2.2Facebook商城的......
  • python自动化内存管理
    引用在编程中,引用是指用来标识、访问或操作某个对象的值的标识符或变量。我们可以将引用看作是对象的别名,通过引用可以操作对象,包括读取、修改和传递对象的值。举例来说,假设我们有一个字符串对象`name`,我们可以创建一个变量`person`来引用这个字符串对象。在这个例子中,`perso......
  • selenium07_select下拉框
    有以下2种方法定位下拉框: 方法一:最基本的元素定位方法,定位下拉框,再定位下拉框中的元素 方法二:fromselenium.webdriver.support.selectimportSelectel=driver.find_element_by_id("nr")  #先定位到下拉框Select(el).select_by_index(0)  #通过下拉框中元素的......
  • LaTeX 编辑协作平台 Overleaf 安装和使用教程
    在学术界和科技行业,LaTeX已成为撰写高质量文档的标准工具。然而,传统的LaTeX使用体验常常伴随着以下挑战:学习曲线陡峭环境配置复杂多人协作困难实时预览不便当然,市面上不乏很多在线LaTeX编辑平台,但它们大多是封闭的商业服务,无法完全满足用户对数据隐私和自主可控的需求......
  • selenium08_鼠标事件、键盘事件
    1.鼠标事件需要导入:fromselenium.webdriver.common.action_chainsimportActionChains1)右击el=driver.find_element_by_id("kw")#定位元素ActionChains(driver).context_click(el).perform()#右击2)双击el= driver.find_element_by_xpath("//div[@id='qrcode�......
  • selenium09_3种等待
    1.time.sleep(2)进程休眠一次有效,一直等待,浪费时间time.sleep(2) 设置2s,等待2s 2.driver.implicitly_wait(10) 隐式等待原理:等当前页面加载完成1)最大等待时间10s2)全局的:脚本中只需写一次,脚本中所有元素定位的场景,都可用3)针对于当前页面的所有元素定位有效缺点:......
  • selenium10_单元测试框架unittest
    一、Python中单元测试框架:unittest框架和pytest框架。本篇记录unittest1.Unittest是通用的,可以做单元测试,接口测试,selenium自动化,app自动化2.需要导入unittest,类继承自unittest.TestCase。3.类名大驼峰;测试方法必须以test开头;测试用例的方法中,不能有参数。4.Pycharm中用un......
  • k8s组件有哪些?
    Kubernetes是谷歌公司一款开源的容器编排管理工具,它的本质是一组服务器集群管理工具,能够在集群的每个节点上运行特定的程序,它的目的是实现资源的管理自动化,主要提供了自我修复,弹性伸缩、服务发现、负载均衡、版本回退、存储编排等功能。1、自我修复:一个容器崩溃,会立......
  • selenium11_js语法
    1.JS语法js中元素定位方法,如下5种方法:a.通过id获取,获取的是单个document.getElementById("id")b.通过name获取,获取的是多个document.getElementsByName("name")[0]c.通过标签名选取元素,获取的是多个document.getElementsByTagName("tag")d.通过CLASS类选取元素,获取的是多个......