[分布式网络通讯框架]----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函数将日志消息写入日志文件中。
  • 72
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值