Bootstrap

详解 Muduo 网络服务和日志模块

这篇文章主要目的是介绍muduo 的网络服务模块和日志模块。

muduo网络服务

在muduo网络库中,最重要的初始化服务,需要涉及两个核心模块,muduo::net::EventLoop和muduo::net::TcpServer。muduo::net::EventLoop不需要我们去设置,我们需要做的只是给Server指定一个EventLoop对象,并在开启Server后开启EventLoop。muduo::net::TcpServer负责muduo网络服务的基础配置,如listenAddr的设置、EventLoop的设置、setConnectionCallback连接回调函数的绑定和setMessageCallback消息回调函数的绑定等。其中绑定的连接回调函数和消息回调函数由我们自定义,定义内容取决于我们的具体需求。

具体的示例如下:

#include "muduo/net/TcpServer.h"
#include "muduo/net/EventLoop.h"

#include <functional>  // 引入functional头文件,用于std::bind函数

using namespace muduo;  // 使用muduo命名空间
using namespace muduo::net;  // 使用muduo::net命名空间

// 定义EchoServer类
class EchoServer 
{
public:
    // 构造函数,接收一个EventLoop指针和一个InetAddress对象
    EchoServer(EventLoop* loop, const InetAddress& listenAddr)
    : loop_(loop),  // 初始化EventLoop指针
      server_(loop, listenAddr, "EchoServer")  // 初始化TcpServer对象
    {
        // 设置连接回调函数,当新的连接建立时,会调用onConnection方法
        server_.setConnectionCallback(std::bind(&EchoServer::onConnection, this, _1));
        // 设置消息回调函数,当接收到新的消息时,会调用onMessage方法
        server_.setMessageCallback(std::bind(&EchoServer::onMessage, this, _1, _2, _3));
    }

    // 启动服务器
    void start()
    {
        server_.start();
    }

private:
    // 连接回调函数,接收一个TcpConnectionPtr智能指针
    void onConnection(const TcpConnectionPtr& conn);

    // 消息回调函数,接收一个TcpConnectionPtr智能指针,一个Buffer指针和一个Timestamp对象
    void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time);

    EventLoop* loop_;  // EventLoop指针
    TcpServer server_;  // TcpServer对象
};

上述例子创建了一个简单的 EchoServer 类,对 muduo 提供的网络服务进行简单的封装。成员变量有 EventLoop* 类型的 loop_ 和 TcpServer 类型的 server_,EventLoop* 对象定义在 TcpServer 之前,因为初始化列表按定义顺序进行初始化,而 TcpServer 依赖于 EventLoop*。可以看到 EventLoop* 对象的创建只需要简单的声明,但是 TcpServer 则需要设置 EventLoop* 、const InetAddress& 、和 const string& nameArg,即事件循环、IP地址和服务名称。除此之外,TcpServer 对象还可以设置回调函数,这是我们需要自定义的部分。

setConnectionCallback 设置连接回调函数,连接回调函数就是当有一个新的连接到达时或者连接断开时会被调用的函数。我们可以在连接回调函数中设置日志或者更新连接状态等。

setMessageCallback 设置消息回调函数,消息回调函数就是当一个新的消息到达时会被调用的函数,我们可以在消息回调函数中处理到达的消息,根据具体的消息内容进行对应处理。

上述示例中的 setConnectionCallback 和 setMessageCallback 绑定的回调函数是 EchoServer 的类成员函数 onConnection 和 onMessage。下面是其简单的实现示例:

void EchoServer::onConnection(const TcpConnectionPtr& conn)
{
    std::cout << conn->peerAddress().toIpPort() << " -> "
                << conn->localAddress().toIpPort() << " is "
                << (conn->connected() ? "UP" : "DOWN") << std::endl;
}

void EchoServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time)
{
    string msg(buf->retrieveAllAsString());
    std::cout << conn->name() << " recv " << msg.size() << " bytes at " << time.toString() << std::endl;
    conn->send(msg);
}

在上述示例中,onConnection 向终端输出对端地址和当前地址连接的状态,onMessage 接收对端发送的消息并转发回去以及向终端输出发送消息的连接名称、消息大小和时间戳。

日志

muduo 提供日志模块记录程序运行的过程信息,并且 muduo 还提供了异步日志模块 AsyncLogging 来防止日志的输出阻塞程序的运行。

在介绍 muduo 日志模块的具体使用示例之前,我们需要了解一下日志等级。

在 muduo 中存在 6 中日志级别:

  enum LogLevel
  {
    TRACE,
    DEBUG,
    INFO,
    WARN,
    ERROR,
    FATAL,
    NUM_LOG_LEVELS,// 日志等级数量
  };

分别是 TRACE, DEBUG, INFO, WARN, ERROR, FATAL。分别用于记录追踪信息、调试信息、一般信息、警告信息、错误信息、致命错误信息,然后日志等级是逐渐递增的,TRACE 最低、FATAL 最高。并且 muduo 提供了对应的宏来方便记录各个级别的日志信息,分别是LOG_TRACE、LOG_DEBUG、LOG_INFO、LOG_WARN、LOG_ERROR、LOG_FATA,除此之外,muduo 还提供 LOG_SYSERR、LOG_SYSFATAL 用于记录系统错误。这八个日志宏中,前三种只有在当前日志等级等于小于对应等级时才会记录日志。如:当日志级别为 INFO 时,使用 LOG_TRACE 和 LOG_DEBUF 来指定的日志信息不会被记录,只有使用 LOG_INFO 及以上等级对应的宏指定的日志才会被记录。后五种日志宏则会忽视当前的日志等级,即无论当前日志等级是什么,都会进行记录。如:当当前日志等级为 FATAL 时,使用 LOG_ERROR 记录的日志照样会被记录。其中,特别的是 LOG_SYSFATAL,它在记录完日志后会立即退出程序(Abort)。

我们可以使用muduo::Logger::setLogLevel(muduo::Logger::LogLevel);来指定日志等级。

下面介绍如何使用日志模块:

我们使用muduo::Logger设置日志系统的基本配置,如输出函数(即使用LOG宏记录日志时,会调用的函数)、日志级别等。当我们需要一个异步日志模块时,我们会使用 AsyncLogging 对象,我们可以指定 AsyncLogging 对象能承载的大小,也就是单个日志文件能承载的大小,当超过这个大小就会创建新的日志文件。

具体的示例代码如下:

下面示例在 网络服务 示例的基础上进行扩展。

//...
void EchoServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) {
    //...
}
// ...

// 设置日志文件的大小为500MB。当日志文件大小达到这个值时,会自动创建一个新的日志文件。
int kRollSize = 500*1000*1000;

// 定义一个全局的unique_ptr,指向一个AsyncLogging对象。AsyncLogging是muduo库中的一个类,用于异步写日志。
std::unique_ptr<muduo::AsyncLogging> g_asyncLog;

// 定义一个函数,用于异步输出日志。这个函数会被设置为Logger类的输出函数。
void asyncOutput(const char* msg, int len) 
{
    // 将日志信息添加到AsyncLogging对象中。AsyncLogging对象会在另一个线程中将这些日志信息写入文件。
    g_asyncLog->append(msg, len); 
}

// 定义一个函数,用于设置日志系统。
void setLogging(const char* argv0) 
{
    // 设置Logger类的输出函数为asyncOutput。这样,当我们使用LOG宏记录日志信息时,这些信息会被传递给asyncOutput函数。
    muduo::Logger::setOutput(asyncOutput); 

    // 设置日志级别为INFO。这样,只有级别大于或等于INFO的日志信息才会被记录。
    muduo::Logger::setLogLevel(muduo::Logger::INFO); 

    char name[256];
    // 将程序的名称复制到name数组中。
    strncpy(name, argv0, 256); 

    // 创建一个新的AsyncLogging对象,并将其赋值给g_asyncLog。这个对象的名称为程序的名称,日志文件的大小为kRollSize。
    g_asyncLog.reset(new muduo::AsyncLogging(::basename(name), kRollSize)); 

    // 启动AsyncLogging对象。这会创建一个新的线程,用于异步写日志。
    g_asyncLog->start(); 
}

上述示例设置了一个异步日志系统,当有日志需要输出时,不直接写日志,而是添加到 muduo::AsyncLogging 对象中,然后由一个单独的线程异步写日志,当日志文件大小达到 50010001000 bytes时,创建新的文件。

以上就是这篇文章的全部内容。

上面示例代码来自于muduo-tutorial,可以前往muduo-tutorial的GitHub仓库查看完整示例代码。

;