C++轻量级Web服务器TinyWebServer源码分析之log篇

news2025/6/18 18:05:09

文章目录

  • log日志篇简介
  • 一、日志类的定义与使用
  • 二、单例模式与阻塞队列的定义
    • 1、单例模式
    • 2、阻塞队列

log日志篇简介

使用单例模式创建日志系统,对服务器运行状态、错误信息和访问数据进行记录,该系统可以实现按天分类,超行分类功能。其中异步写入方式,将生产者-消费者模型封装为阻塞队列,创建一个写线程,工作线程将要写的内容push进队列,写线程从队列中取出内容,写入日志文件。

日志,由服务器自动创建,并记录运行状态,错误信息,访问数据的文件
异步日志,将所写的日志内容先存入阻塞队列,写线程从阻塞队列中取出内容,写入日志。
同步日志,日志写入函数与工作线程串行执行
生产者-消费者模型,生产者线程与消费者线程共享一个缓冲区,生产者线程往缓冲区中push消息,消费者线程从缓冲区中pop消息。
阻塞队列,将生产者-消费者模型进行封装,使用循环数组实现队列,作为两者共享的缓冲区。
单例模式,保证一个类只创建一个实例,同时提供全局访问的方法。

在这里插入图片描述

一、日志类的定义与使用

通过局部变量的懒汉单例模式创建日志实例,对其进行初始化生成日志文件后,格式化输出内容,并根据不同的写入方式,完成对应逻辑,写入日志文件。

类头文件源码如下:

#include <stdio.h>
#include <iostream>
#include <string>
#include <stdarg.h>
#include <pthread.h>
#include "block_queue.h"

using namespace std;

class Log
{
public:
    //C++11以后,使用局部变量懒汉不用加锁
    static Log *get_instance()
    {
        static Log instance;
        return &instance;
    }

    static void *flush_log_thread(void *args)
    {//异步写日志公有方法,调用私有方法async_write_log
        Log::get_instance()->async_write_log();
    }
    //可选择的参数有日志文件、日志缓冲区大小、最大行数以及最长日志条队列
    bool init(const char *file_name, int close_log, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0);

    void write_log(int level, const char *format, ...);//将输出内容按照标准格式整理

    void flush(void);//强制刷新缓冲区

private:
    Log();
    virtual ~Log();
    void *async_write_log()
    {//异步写日志方法
        string single_log;
        
        while (m_log_queue->pop(single_log))
        {//从阻塞队列中取出一个日志string,写入文件
            m_mutex.lock();
            fputs(single_log.c_str(), m_fp);
            m_mutex.unlock();
        }
    }

private:
    char dir_name[128]; //路径名
    char log_name[128]; //log文件名
    int m_split_lines;  //日志最大行数
    int m_log_buf_size; //日志缓冲区大小
    long long m_count;  //日志行数记录
    int m_today;        //因为按天分类,记录当前时间是那一天
    FILE *m_fp;         //打开log的文件指针
    char *m_buf;		//要输出的内容
    block_queue<string> *m_log_queue; //阻塞队列
    bool m_is_async;                  //是否同步标志位
    locker m_mutex;					  //同步类
    int m_close_log; 				  //关闭日志
};

//这四个宏定义在其他文件中使用,主要用于不同类型的日志输出对日志等级进行分类,包括DEBUG,INFO,WARN和ERROR四种级别的日志
#define LOG_DEBUG(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(0, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_INFO(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(1, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_WARN(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(2, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_ERROR(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(3, format, ##__VA_ARGS__); Log::get_instance()->flush();}

日志分级的实现大同小异,一般的会提供五种级别,具体的,

Debug,调试代码时的输出,在系统实际运行时,一般不使用。

Warn,这种警告与调试时终端的warning类似,同样是调试代码时使用。

Info,报告系统当前的状态,当前执行的流程或接收的信息等。

Error和Fatal,输出系统的错误信息。

本项目中给出了除Fatal外的四种分级,实际使用了Debug,Info和Error三种。

超行、按天分文件逻辑,具体的,

日志写入前会判断当前day是否为创建日志的时间,行数是否超过最大行限制

若为创建日志时间,写入日志,否则按当前时间创建新log,更新创建时间和行数

若行数超过最大行限制,在当前日志的末尾加count/max_lines为后缀创建新log

将系统信息格式化后输出,具体为:格式化时间 + 格式化内容

类具体实现源码如下:
在这里插入图片描述
init函数实现日志创建、写入方式的判断。

write_log函数完成写入日志文件中的具体内容,主要实现日志分级、分文件、格式化输出内容。

生成日志文件 && 判断写入方式
通过单例模式获取唯一的日志类,调用init方法,初始化生成日志文件,服务器启动按当前时刻创建日志,前缀为时间,后缀为自定义log文件名,并记录创建日志的时间day和行数count。

写入方式通过初始化时是否设置队列大小(表示在队列中可以放几条数据)来判断,若队列大小为0,则为同步,否则为异步。

二、单例模式与阻塞队列的定义

1、单例模式

实现思路:私有化它的构造函数,以防止外界创建单例类的对象;使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。

单例模式有两种实现方法,分别是懒汉和饿汉模式。顾名思义,懒汉模式,即非常懒,不用的时候不去初始化,所以在第一次被使用时才进行初始化;饿汉模式,即迫不及待,在程序运行时立即初始化。

经典的懒汉模式使用双重检查锁创建,然鹅也可以使用函数内的局部静态对象创建

如果只检测一次,在每次调用获取实例的方法时,都需要加锁,这将严重影响程序性能。双层检测可以有效避免这种情况,仅在第一次创建单例的时候加锁,其他时候都不再符合NULL == p的情况,直接返回已创建好的实例。

class single{
private:
    single(){}
    ~single(){}
public:
     static single* getinstance();
};

single* single::getinstance(){
    static single obj;
    return &obj;
}

C++0X以后由编译器保证内部静态变量的线程安全性,C++0X之前仍然需要加锁

class single{
private:
    static pthread_mutex_t lock;
    single(){
        pthread_mutex_init(&lock, NULL);
    }
    ~single(){}

public:
    static single* getinstance();

};
pthread_mutex_t single::lock;
single* single::getinstance(){
    pthread_mutex_lock(&lock);
    static single obj;
    pthread_mutex_unlock(&lock);
    return &obj;
}

饿汉模式不需要用锁,就可以实现线程安全。原因在于,在程序运行时就定义了对象,并对其初始化。之后,不管哪个线程调用成员函数getinstance(),都只不过是返回一个对象的指针而已。所以是线程安全的,不需要在获取实例的成员函数中加锁。
饿汉模式虽好,但其存在隐藏的问题,在于非静态对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。

2、阻塞队列

阻塞队列主要是用于生产者消费者模型,可以使用queue也可以自定义,这里采用的自定义的方式;

条件变量提供了一种线程间的通知机制,当某个共享数据达到某个值时,唤醒等待这个共享数据的线程。
重点一

pthread _mutex_lock(&mutex)
while(线程执行的条件是否成立){
    pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);

pthread_cond_wait执行后的内部操作分为以下几步:

1、将线程放在条件变量的请求队列后,内部解锁

2、线程等待被pthread_cond_broadcast信号唤醒或者pthread_cond_signal信号唤醒,唤醒后去竞争锁

3、若竞争到互斥锁,内部再次加锁

使用前加锁是为了避免资源竞争,让每个线程互斥的访问公有资源;
内部解锁是因为当前线程执行pthread_cond_wait时,一定是处于某个临界区,正在访问共享资源,存在一个mutex与该临界区相关联。因此,在阻塞前,必须释放mutex;
被唤醒后,仍然处于临界区,因此需要再次获得mutex。

重点二
为什么判断线程执行的条件用while而不是if?
一般来说,在多线程资源竞争的时候,在一个使用资源的线程里面(消费者)判断资源是否可用,不可用,便调用pthread_cond_wait,在另一个线程里面(生产者)如果判断资源可用的话,则调用pthread_cond_signal发送一个资源可用信号。

在wait成功之后,资源就一定可以被使用么?答案是否定的,如果同时有两个或者两个以上的线程正在等待此资源,wait返回后,资源可能已经被使用了。

再具体点,有可能多个线程都在等待这个资源可用的信号,信号发出后只有一个资源可用,但是有A,B两个线程都在等待,B比较速度快,获得互斥锁,然后加锁,消耗资源,然后解锁,之后A获得互斥锁,但A回去发现资源已经被使用了,它便有两个选择,一个是去访问不存在的资源,另一个就是继续等待,那么继续等待下去的条件就是使用while,要不然使用if的话pthread_cond_wait返回后,就会顺序执行下去。

所以,在这种情况下,应该使用while而不是if:

自定义队列源码分析:
阻塞队列类中封装了生产者-消费者模型,其中push成员是生产者,pop成员是消费者。
阻塞队列中,使用了循环数组实现了队列,作为两者共享缓冲区,当然了,队列也可以使用STL中的queue。

当队列为空时,从队列中获取元素的线程将会被挂起;当队列是满时,往队列里添加元素的线程将会挂起。

/*************************************************************
*循环数组实现的阻塞队列,m_back = (m_back + 1) % m_max_size;  
*线程安全,每个操作前都要先加互斥锁,操作完后,再解锁
**************************************************************/

#ifndef BLOCK_QUEUE_H
#define BLOCK_QUEUE_H

#include <iostream>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include "../lock/locker.h"
using namespace std;

template <class T>
class block_queue
{
public:
    block_queue(int max_size = 1000)
    {
        if (max_size <= 0)
        {
            exit(-1);
        }

        m_max_size = max_size;
        m_array = new T[max_size];
        m_size = 0;
        m_front = -1;
        m_back = -1;
    }

    void clear()
    {
        m_mutex.lock();
        m_size = 0;
        m_front = -1;
        m_back = -1;
        m_mutex.unlock();
    }

    ~block_queue()
    {
        m_mutex.lock();
        if (m_array != NULL)
            delete [] m_array;

        m_mutex.unlock();
    }
    //判断队列是否满了
    bool full() 
    {
        m_mutex.lock();
        if (m_size >= m_max_size)
        {

            m_mutex.unlock();
            return true;
        }
        m_mutex.unlock();
        return false;
    }
    //判断队列是否为空
    bool empty() 
    {
        m_mutex.lock();
        if (0 == m_size)
        {
            m_mutex.unlock();
            return true;
        }
        m_mutex.unlock();
        return false;
    }
    //返回队首元素
    bool front(T &value) 
    {
        m_mutex.lock();
        if (0 == m_size)
        {
            m_mutex.unlock();
            return false;
        }
        value = m_array[m_front];
        m_mutex.unlock();
        return true;
    }
    //返回队尾元素
    bool back(T &value) 
    {
        m_mutex.lock();
        if (0 == m_size)
        {
            m_mutex.unlock();
            return false;
        }
        value = m_array[m_back];
        m_mutex.unlock();
        return true;
    }

    int size() 
    {
        int tmp = 0;

        m_mutex.lock();
        tmp = m_size;

        m_mutex.unlock();
        return tmp;
    }

    int max_size()
    {
        int tmp = 0;

        m_mutex.lock();
        tmp = m_max_size;

        m_mutex.unlock();
        return tmp;
    }
    //往队列添加元素,需要将所有使用队列的线程先唤醒
    //当有元素push进队列,相当于生产者生产了一个元素
    //若当前没有线程等待条件变量,则唤醒无意义
    bool push(const T &item)
    {

        m_mutex.lock();
        if (m_size >= m_max_size)
        {

            m_cond.broadcast();
            m_mutex.unlock();
            return false;
        }

        m_back = (m_back + 1) % m_max_size;
        m_array[m_back] = item;

        m_size++;

        m_cond.broadcast();
        m_mutex.unlock();
        return true;
    }
    //pop时,如果当前队列没有元素,将会等待条件变量
    bool pop(T &item)
    {

        m_mutex.lock();
        while (m_size <= 0)
        {
            
            if (!m_cond.wait(m_mutex.get()))
            {
                m_mutex.unlock();
                return false;
            }
        }

        m_front = (m_front + 1) % m_max_size;
        item = m_array[m_front];
        m_size--;
        m_mutex.unlock();
        return true;
    }

    //增加了超时处理
    bool pop(T &item, int ms_timeout)
    {
        struct timespec t = {0, 0};
        struct timeval now = {0, 0};
        gettimeofday(&now, NULL);
        m_mutex.lock();
        if (m_size <= 0)
        {
            t.tv_sec = now.tv_sec + ms_timeout / 1000;
            t.tv_nsec = (ms_timeout % 1000) * 1000;
            if (!m_cond.timewait(m_mutex.get(), t))
            {
                m_mutex.unlock();
                return false;
            }
        }

        if (m_size <= 0)
        {
            m_mutex.unlock();
            return false;
        }

        m_front = (m_front + 1) % m_max_size;
        item = m_array[m_front];
        m_size--;
        m_mutex.unlock();
        return true;
    }

private:
    locker m_mutex;
    cond m_cond;

    T *m_array;
    int m_size;
    int m_max_size;
    int m_front;
    int m_back;
};

#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/411764.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RabbitMq图形界面创建队列操作步骤及控制台使用说明

版本&#xff1a;RabbitMQ 3.9.7 控台台访问路径&#xff1a; http://localhost:15672/#/queues 使用控制台创建队列 登录 创建队列 &#xff08;1&#xff09;输入自定义的队列名称 &#xff08;2&#xff09;其他输入参数为默认值即可 &#xff08;3&#xff09;点击【Add…

雷蛇灵刃18 2023原厂预装出厂Windows11系统

雷蛇系统安装完自带所有机型驱动和软件&#xff0c;并重建隐藏分区&#xff0c;还原功能 文件地址: https://pan.baidu.com/s/1snKOsH3OMl3GZLqeAf-GLA?pwd8888 支持系列: 雷蛇灵刃16 2023 [RZ09-0483]Windows11原厂系统 雷蛇灵刃 Stealth 13 Base Model [RZ09-0310] 201…

【Unity VR开发】结合VRTK4.0:创建一个按钮(Option Button)

语录&#xff1a; 如同天上降魔主&#xff0c;真是人间太岁神。 前言&#xff1a; 选项按钮是一种提供多项选择选项的方法&#xff0c;其中只有一个按钮可以处于激活状态&#xff0c;激活另一个按钮时将确保组中的所有其他按钮都已停用。我们可以使用嵌套在预制件中的预制件来实…

ChatGPT将引发大量而普遍的网络安全隐患

ChatGPT是一个基于人工智能的语言生成模型&#xff0c;它可以在任何给定的时间&#xff0c;使用自然语言生成技术&#xff0c;生成文本、对话和文章。它不仅可以被用来编写文本&#xff0c;还可以用来编写语言、生成图像和视频。目前&#xff0c; ChatGPT已广泛应用于语言翻译、…

FPGA lattice 深力科LCMXO3LF-4300C-6BG256I 可实现高效、灵活和安全的工业应用开发 低功耗FPGA解决方案详情讲解

FPGA lattice 深力科LCMXO3LF-4300C-6BG256I 可实现高效、灵活和安全的工业应用开发 低功耗FPGA解决方案详情讲解 超低密度FPGA 是最新的立即启用、非挥发性、小型覆盖区 FPGA&#xff0c;采用先进的封装技术&#xff0c;能让每个元件达到最低成本。此系列采用最新的小型封装&…

RK3399平台开发系列讲解(基础篇)Linux 传统间隔定时器

🚀返回专栏总目录 文章目录 一、设置间隔定时器 setitimer()二、查询定时器状态 getitimer()三、更简单的定时接口 alarm()四、传统定时器的应用4.1、为阻塞操作设置超时4.2、性能剖析五、传统定时器的局限性沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将详细…

java 泛型 万字详解(通俗易懂)

目录 一、前言 二、为什么需要泛型&#xff1f; 三、什么是泛型&#xff1f; 1.泛型的定义 : 2.泛型的作用 : 四、怎么用泛型&#xff1f; 1.泛型的语法 : 2. 泛型的使用 : 3.自定义泛型类 : 1 基本语法 : 2 使用细节 : 4.自定义泛型接口 : 1 基本语法 : 2 使用细…

C++-继承

继承继承的基本概念继承的概念继承的定义继承的格式继承的方式继承基类成员访问方式的变化基类与派生类的对象赋值转换继承中的作用域派生类中的默认成员函数继承与友元继承中的静态成员菱形继承菱形虚拟继承继承的总结继承的基本概念 继承的概念 继承机制是面向对象程序设计中…

【Spring源码】 BeanFactory和FactoryBean是什么?

1、前言 面试官&#xff1a;“看过Spring源码吧&#xff0c;简单说说Spring中BeanFactory和FactoryBean的区别是什么&#xff1f;” 大神仙&#xff1a;“BeanFactory是bean工厂&#xff0c;FactoryBean是工厂bean”。 这么回答&#xff0c;等于面试官问你Spring是什么&…

如何免费使用ChatGPT 4?

自从ChatGPT发布以来&#xff0c;它就取得了巨大的成功。无论是常春藤法学考试还是商学院作业&#xff0c;ChatGPT都被用于各种试验。统计数据显示&#xff0c;ChatGPT每月吸引约9600万用户。随着ChatGPT的巨大成功&#xff0c;Open AI最近推出了它的最新版本&#xff0c;名为“…

Learning to Detect Human-Object Interactions 文章解读

Learning to Detect Human-Object Interactions&#xff0c;WACV&#xff0c;2018 论文下载 code&#xff1a;http://www.umich.edu/∼ywchao/hico/ 摘要 主要研究领域&#xff1a;定义了HOI detection任务&#xff1a;在静态图像中检测人-对象交互&#xff08;HOI&#xff…

Vue路由模式为history的项目部署到Nginx

前言 对于前端工程师而言&#xff0c;多多少少会碰到按需加载的需求。 比如一个系统&#xff0c;需要用户登陆以后才能使用&#xff0c;对于传统的前后端未分离的情况&#xff0c;我们一般的处理方式是&#xff0c;当检测到用户未登录的时候&#xff0c;一般会重定向到登录页面…

JVM运行时数据区的必备知识:Java程序员不容错过

1、JVM运行时数据区概念 JVM运行时数据区是Java虚拟机在执行Java程序时所使用的内存区域。这些区域包括了以下几个部分&#xff1a; 程序计数器&#xff08;Program Counter Register&#xff09;&#xff1a;程序计数器是一块较小的内存区域&#xff0c;它可以看作是当前线程…

测试1号位的自我修养

作者&#xff1a;京东零售 吴聪 引言 目前京东实行BigBoss机制以及积木型组织&#xff0c;同时现阶段再次强调了“经营”理念&#xff0c;以上均是比较大的组织层面的纲领和引导&#xff0c;核心是为了激发大家owner意识可以更好更快为公司产出价值和贡献。落到具体执行层面&…

国内大模型领域进入乱战时代

国内大模型领域进入乱战时代 2023.4.12版权声明&#xff1a;本文为博主chszs的原创文章&#xff0c;未经博主允许不得转载。 什么是大模型 大模型&#xff0c;又称为预训练模型、基础模型等&#xff0c;是指模型参数数量很大&#xff0c;需要大量计算资源才能训练的深度学习…

RHCE-Web服务器

请给openlab搭建web网站​ 网站需求&#xff1a;​ 1.基于域名[www.openlab.com](http://www.openlab.com)可以访问网站内容为 welcome to openlab!!! 首先创建一个名为openlab的网站&#xff1a; &#xff08;1&#xff09;在www目录下创建一个openlab文件夹&#xff1a;mk…

Android UI

什么是 UI 用户界面&#xff08;User Interface&#xff0c;简称 UI&#xff0c;亦称使用者界面&#xff09;是系统和用户之间进行交互和信息交换的媒介&#xff0c;它实现信息的内部形式与人类可以接受形式之间的转换。软件设计可分为两个部分&#xff1a;编码设计与UI设计。A…

JavaScript编程实现tab选项卡切换的效果+1

之前在“圳品”信息系统使用了tab选项卡来显示信息&#xff0c;详见&#xff1a; JavaScript编程实现tab选项卡切换的效果 在tab选项卡中使用其它<div>来显示信息就出现了问题&#xff0c;乱套了&#xff0c;比如下面的这段代码&#xff1a; <!DOCTYPE html> &l…

c/c++:for循环语句,分号不可省略,表达式可以省略,猜数字游戏,跳转语句continue,break,避免写goto

c/c:for循环语句&#xff0c;分号不可省略&#xff0c;表达式可以省略&#xff0c;猜数字游戏&#xff0c;跳转语句continue&#xff0c;break&#xff0c;避免写goto 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;此时学…

树莓派 QT项目开机自启动

我自己用qt设置了一个界面&#xff0c;如何让他开机自启动呢&#xff1f; 目录 1.生成qt项目的可执行文件 2. 编写一个自启动脚本 3.重启树莓派 1.生成qt项目的可执行文件 QT项目的可执行文件就是.exe文件。首先在qt中打开&#xff0c;点击红色方框图标&#xff0c;选择Re…