Muduo源码Base篇

Muduo异步日志

先来看AsyncLogging类的定义

class AsyncLogging : noncopyable
{
 public:

  AsyncLogging(const string& basename,
               off_t rollSize,
               int flushInterval = 3);

  ~AsyncLogging()
  {
    if (running_)
    {
      stop();
    }
  }

  void append(const char* logline, int len);

  void start()
  {
    running_ = true;
    thread_.start();
    latch_.wait();
  }

  void stop() NO_THREAD_SAFETY_ANALYSIS
  {
    running_ = false;
    cond_.notify();
    thread_.join();
  }

 private:

  void threadFunc();

  typedef muduo::detail::FixedBuffer<muduo::detail::kLargeBuffer> Buffer; //缓冲区大小 4000*1000
  typedef std::vector<std::unique_ptr<Buffer>> BufferVector;
  typedef BufferVector::value_type BufferPtr;

  const int flushInterval_;//
  std::atomic<bool> running_;
  const string basename_;
  const off_t rollSize_;//滚动大小
  muduo::Thread thread_;
  muduo::CountDownLatch latch_;
  muduo::MutexLock mutex_;
  muduo::Condition cond_ GUARDED_BY(mutex_);
  BufferPtr currentBuffer_ GUARDED_BY(mutex_);
  BufferPtr nextBuffer_ GUARDED_BY(mutex_);
  BufferVector buffers_ GUARDED_BY(mutex_);
};
}  // namespace muduo

再看这个类具体实现之前 先来看里面用到的辅助类

class LogFile : noncopyable
{
 public:
  LogFile(const string& basename,
          off_t rollSize,
          bool threadSafe = true,
          int flushInterval = 3,
          int checkEveryN = 1024);
  ~LogFile();

  void append(const char* logline, int len);
  void flush();
  bool rollFile();

 private:
  void append_unlocked(const char* logline, int len);

  static string getLogFileName(const string& basename, time_t* now);

  const string basename_;
  const off_t rollSize_;//写入的大小
  const int flushInterval_;
  const int checkEveryN_;//该文件中的日志条数

  int count_;

  std::unique_ptr<MutexLock> mutex_;
  time_t startOfPeriod_;
  time_t lastRoll_;
  time_t lastFlush_;
  std::unique_ptr<FileUtil::AppendFile> file_;

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

其中FileUtil类如下

class AppendFile : noncopyable
{
 public:
  explicit AppendFile(StringArg filename);

  ~AppendFile();

  void append(const char* logline, size_t len);

  void flush();

  off_t writtenBytes() const { return writtenBytes_; }

 private:

  size_t write(const char* logline, size_t len);

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

}  // namespace FileUtil
}  // namespace muduo

其中StringArg类是一个传递c语言的字符串的类型的类定义如下

class StringArg // copyable
{
 public:
  StringArg(const char* str)
    : str_(str)
  { }

  StringArg(const string& str)
    : str_(str.c_str())
  { }

  const char* c_str() const { return str_; }

 private:
  const char* str_;
};

AppendFile的构造函数如下

FileUtil::AppendFile::AppendFile(StringArg filename)
  : fp_(::fopen(filename.c_str(), "ae")),  // 'e' for O_CLOEXEC
    writtenBytes_(0)
{
  assert(fp_);
  ::setbuffer(fp_, buffer_, sizeof buffer_);
  // posix_fadvise POSIX_FADV_DONTNEED ?
}

setbuffer设置文件流对应的缓冲区大小
如下例子



#include<iostream>
#include<cstring>
#include<unistd.h>
using namespace std;

int main(){

    char *buf=(char*)malloc(sizeof(char)*1024*10);
    setbuffer(stdout,buf,1024*10);
    char buffer[10];
    memset(buffer,'a',sizeof(buffer));
    for(int i=0;i<1000;++i)
        printf("%s",buffer);
    sleep(10);
    while(1);
}

此处永远不会有输出 因为标准输出属于行缓冲 现将缓冲区大小改为1024的10倍 总共输出了10000字节 缓冲区未满 而且没有换行符出现 程序是个死循环 无法结束 导致无法输出
一般而言 对于终端 诸如标准输入 标准输出属于行缓冲 遇到换行符或者缓冲区满了 才会输出
标准出错不带缓冲 以便直接打印错误信息

再来看AppendFile的构造函数 将缓冲区设置为buffer_的大小
append写入logline的len字节

void FileUtil::AppendFile::append(const char* logline, const size_t len)
{
  size_t written = 0;

  while (written != len)
  {
    size_t remain = len - written;
    size_t n = write(logline + written, remain);
    if (n != remain)
    {
      int err = ferror(fp_);
      if (err)
      {
        fprintf(stderr, "AppendFile::append() failed %s\n", strerror_tl(err));
        break;
      }
    }
    written += n;
  }

  writtenBytes_ += written;
}

write函数如下


size_t FileUtil::AppendFile::write(const char* logline, size_t len)
{
  // #undef fwrite_unlocked
  return ::fwrite_unlocked(logline, 1, len, fp_);
}

fwrite_unlocked为fwrite的线程不安全版本 至于这里为什么使用这个 还要等到分析AsyncLogging类才能揭晓
接着来看之前的LogFile类的构造函数

LogFile::LogFile(const string& basename,
                 off_t rollSize,
                 bool threadSafe,
                 int flushInterval,
                 int checkEveryN)
  : basename_(basename),//文件名
    rollSize_(rollSize),//最大字节
    flushInterval_(flushInterval),//刷新间隔 即flush间隔
    checkEveryN_(checkEveryN),//最大日志条数
    count_(0),//当前日志条数
    mutex_(threadSafe ? new MutexLock : NULL),
    startOfPeriod_(0),//前一天时间
    lastRoll_(0),//上次刷新时间
    lastFlush_(0)
{
//检查basename是否合法
  assert(basename.find('/') == string::npos);
  rollFile();
}
bool LogFile::rollFile()
{
  time_t now = 0;
  string filename = getLogFileName(basename_, &now);//获得文件名称
  time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;(now-now%KRollPerSeconds_)

  if (now > lastRoll_)
  {
    lastRoll_ = now;
    lastFlush_ = now;
    startOfPeriod_ = start;
    file_.reset(new FileUtil::AppendFile(filename));//换一个文件
    return true;
  }
  return false;
}
//生成日志的文件名
string LogFile::getLogFileName(const string& basename, time_t* now)
{
  string filename;
  filename.reserve(basename.size() + 64);
  filename = basename;

  char timebuf[32];
  struct tm tm;
  *now = time(NULL);
  gmtime_r(now, &tm); // FIXME: localtime_r ?
  strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S.", &tm);
  filename += timebuf;

  filename += ProcessInfo::hostname();

  char pidbuf[32];
  snprintf(pidbuf, sizeof pidbuf, ".%d", ProcessInfo::pid());
  filename += pidbuf;

  filename += ".log";

  return filename;
}

向当前日志中添加信息append函数如下

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

此处利用了MutexLockGuard上锁 因此append_unlocked也就不需要任何加锁操作 正是在这个函数种调用了AppendFile的append 继而调用fwrite_unlocked函数

void LogFile::append_unlocked(const char* logline, int len)
{
  file_->append(logline, len);

  if (file_->writtenBytes() > rollSize_)
  {
    rollFile();
  }
  else
  {
    ++count_;
    if (count_ >= checkEveryN_)
    {
      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();
      }
    }
  }
}

当作为缓冲区的AppendFile的大小大与当前rollSize则需要重新生成一个新的日志文件
否则 更新日志条数 当前日志条数如果大于最大值
天数不一样则重新换一个日志写 或者当前与之前刷新的时间间隔大于设置的时间间隔 则刷新到磁盘中

注意!!当写入日志在文件中过快时 会导致实际写入的文件大小与预期的文件大小不一样 尽管每次都rollFile但是可能多次获得的filename是同一个 导致和预期大小差的较多

在多线程程序中常用的日志 并不是直接进行磁盘IO 而是采用异步日志的方法 单独开启一个线程去做日志的写入操作

现在继续来看AsynLogging类 采取双缓冲

AsyncLogging::AsyncLogging(const string& basename,
                           off_t rollSize,
                           int flushInterval)
  : flushInterval_(flushInterval),//flush时间间隔
    running_(false),//线程是否运行
    basename_(basename),
    rollSize_(rollSize),//日志大致大小 写入过快导致大小不符
    thread_(std::bind(&AsyncLogging::threadFunc, this), "Logging"),
    latch_(1),//必须的一步
    mutex_(),
    cond_(mutex_),
    currentBuffer_(new Buffer),
    nextBuffer_(new Buffer),
    buffers_()
{
  currentBuffer_->bzero();
  nextBuffer_->bzero();
  buffers_.reserve(16);
}

append函数

void AsyncLogging::append(const char* logline, int len)
{
  muduo::MutexLockGuard lock(mutex_);
  if (currentBuffer_->avail() > len)
  {
    currentBuffer_->append(logline, len);
  }
  else
  {
    buffers_.push_back(std::move(currentBuffer_));

    if (nextBuffer_)
    {
      currentBuffer_ = std::move(nextBuffer_);
    }
    else
    {
      currentBuffer_.reset(new Buffer); // Rarely happens
    }
    currentBuffer_->append(logline, len);
    cond_.notify();
  }
}

前端操作:当前buffer能容纳下数据则直接加入 否则利用下一块buffer 将其移为当前buffer
后端操作

void AsyncLogging::threadFunc()
{
  assert(running_ == true);
  latch_.countDown();
  LogFile output(basename_, rollSize_, false);
  BufferPtr newBuffer1(new Buffer);
  BufferPtr newBuffer2(new Buffer);
  newBuffer1->bzero();
  newBuffer2->bzero();
  BufferVector buffersToWrite;
  buffersToWrite.reserve(16);
  while (running_)
  {
    assert(newBuffer1 && newBuffer1->length() == 0);
    assert(newBuffer2 && newBuffer2->length() == 0);
    assert(buffersToWrite.empty());

    {
      muduo::MutexLockGuard lock(mutex_);
      //满足两种条件之一即可
      if (buffers_.empty())  // unusual usage!
      {
        cond_.waitForSeconds(flushInterval_);
      }
      buffers_.push_back(std::move(currentBuffer_));
      currentBuffer_ = std::move(newBuffer1);
      buffersToWrite.swap(buffers_);
      if (!nextBuffer_)
      {
        nextBuffer_ = std::move(newBuffer2);
      }
    }

    assert(!buffersToWrite.empty());

    if (buffersToWrite.size() > 25)
    {
      char buf[256];
      snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger buffers\n",
               Timestamp::now().toFormattedString().c_str(),
               buffersToWrite.size()-2);
      fputs(buf, stderr);
      output.append(buf, static_cast<int>(strlen(buf)));
      buffersToWrite.erase(buffersToWrite.begin()+2, buffersToWrite.end());
    }

    for (const auto& buffer : buffersToWrite)
    {
      // FIXME: use unbuffered stdio FILE ? or use ::writev ?
      output.append(buffer->data(), buffer->length());
    }

    if (buffersToWrite.size() > 2)
    {
      // drop non-bzero-ed buffers, avoid trashing
      buffersToWrite.resize(2);
    }

    if (!newBuffer1)
    {
      assert(!buffersToWrite.empty());
      newBuffer1 = std::move(buffersToWrite.back());
      buffersToWrite.pop_back();
      newBuffer1->reset();
    }

    if (!newBuffer2)
    {
      assert(!buffersToWrite.empty());
      newBuffer2 = std::move(buffersToWrite.back());
      buffersToWrite.pop_back();
      newBuffer2->reset();
    }

    buffersToWrite.clear();
    output.flush();
  }
  output.flush();
}

后端利用writeBufferVector缩短临界区 将当前buffer加入到buffers_中 newBuffer移为当前buffer 如果前端写入数据过多 导致nextBuffer也使用了 则将newBuffer2移为nextBuffer 交换buffers_和writeBufferVector 退出缓冲区 之后保证有两个缓冲区可用即newBuffer1 newBuffer2

标题基于PHP + JavaScript的助眠小程序设计与实现AI更换标题第1章引言介绍助眠小程序的研究背景、意义,以及论文的研究内容和创新点。1.1研究背景与意义阐述助眠小程序在当前社会的重要性和应用价值。1.2国内外研究现状分析国内外在助眠小程序方面的研究进展及现状。1.3论文研究内容与创新点概述论文的主要研究内容和创新之处。第2章相关理论基础介绍助眠小程序设计与实现所涉及的相关理论基础。2.1PHP编程技术阐述PHP编程技术的基本概念、特点和在助眠小程序中的应用。2.2JavaScript编程技术介绍JavaScript编程技术的核心思想、作用及在小程序中的实现方式。2.3小程序设计原理讲解小程序的设计原则、架构和关键技术。第3章助眠小程序需求分析对助眠小程序进行详细的需求分析,为后续设计与实现奠定基础。3.1用户需求调研用户需求调研的过程和方法,总结用户需求。3.2功能需求分析根据用户需求,分析并确定助眠小程序的核心功能和辅助功能。3.3性能需求分析明确助眠小程序在性能方面的要求,如响应速度、稳定性等。第4章助眠小程序设计详细阐述助眠小程序的设计方案,包括整体架构、功能模块和界面设计。4.1整体架构设计给出助眠小程序的整体架构设计思路和实现方案。4.2功能模块设计详细介绍各个功能模块的设计思路和实现方法。4.3界面设计阐述助眠小程序的界面设计风格、布局和交互设计。第5章助眠小程序实现与测试讲解助眠小程序的实现过程,并进行详细的测试与分析。5.1开发环境搭建与配置介绍开发环境的搭建过程和相关配置信息。5.2代码实现与优化详细阐述助眠小程序的代码实现过程,包括关键技术的运用和优化措施。5.3测试与性能分析对助眠小程序进行全面的测试,包括功能测试、性能测试等,并分析测试结果。第6章结论与展望总结论文的研究成果,展望未来的研究方向和应用前景。6.1研究成果总结概括性地总结论
### 安装muduo网络库的详细指南 #### 准备工作 为了成功安装 `muduo` 网络库,需要确保系统已经安装了必要的依赖项。由于 `muduo` 是基于 `Boost` 库构建的,因此必须先完成 `Boost` 的安装[^1]。 --- #### Step 1: 安装 Boost 库 可以通过以下方法在 Linux 上安装 `Boost`: 1. **下载 Boost 源码包** 访问官方网站获取最新版本的 Boost 源码压缩包并解压: ```bash wget https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.gz tar -xvzf boost_1_80_0.tar.gz cd boost_1_80_0 ``` 2. **配置和编译 Boost** 使用内置脚本进行配置和编译: ```bash ./bootstrap.sh --prefix=/usr/local sudo ./b2 install ``` 这一步会将 Boost 安装到 `/usr/local/include` 和 `/usr/local/lib` 中。 --- #### Step 2: 下载 muduo 源码 访问 GitHub 或其他资源站点下载 `muduo` 源码包,并将其解压至目标目录: ```bash git clone https://github.com/chenshuo/muduo.git cd muduo ``` --- #### Step 3: 编译 muduo 执行以下命令以编译 `muduo` 源码: ```bash make all sudo make install ``` 这将在默认路径(通常是 `/usr/local/include` 和 `/usr/local/lib`)中安装头文件和静态链接库。 --- #### Step 4: 测试安装 创建一个简单的测试程序验证 `muduo` 是否正常运行。例如编写如下代码保存为 `test_muduo.cpp`: ```cpp #include "muduo/net/TcpServer.h" #include "muduo/base/Logging.h" using namespace muduo; using namespace muduo::net; int main() { LOG_INFO << "Starting server..."; EventLoop loop; InetAddress listenAddr(9981); TcpServer server(&loop, listenAddr, "TestMuduo"); server.start(); loop.loop(); return 0; } ``` 编译该程序时需指定 `-lmuduo` 和 `-lpthread` 参数: ```bash g++ test_muduo.cpp -std=c++11 -I/usr/local/include -L/usr/local/lib -lmuduo -lboost_system -pthread -o test_muduo ./test_muduo ``` 如果服务器能够启动,则说明安装成功[^3]。 --- #### 常见问题排查 - 如果遇到找不到 `libboost_system.so` 错误,请确认已正确设置环境变量 `LD_LIBRARY_PATH`: ```bash export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH ``` - 若编译失败提示缺少某些头文件或库,请重新检查 Boost 及其子模块是否完全安装。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值