Muduo网络库介绍

1.Reactor介绍

1.回调函数

**回调(Callback)**是一种编程技术,允许将一个函数作为参数传递给另一个函数,并在适当的时候调用该函数

1.工作原理

  1. 定义回调函数

  2. 注册回调函数

  3. 触发回调

2.优点

  1. 异步编程

    • 回调函数允许在事件发生时执行特定的逻辑,而无需阻塞主线程,特别适合实现异步编程。

    • 例如,在网络编程中,当数据可读或可写时调用回调函数,而无需阻塞等待。

  2. 事件驱动

    • 回调函数是事件驱动编程的核心机制,可以高效地处理事件,提高系统的响应速度和吞吐量。

    • 例如,在 GUI 编程中,按钮点击事件会触发回调函数。

  3. 代码复用

    • 回调函数可以独立定义和复用,提高了代码的可复用性和可维护性。

    • 例如,同一个回调函数可以用于多个事件处理。

3.缺点

  1. 回调地狱: 当回调函数嵌套过深时,代码结构会变得混乱,难以理解和维护,这种现象被称为“回调地狱”。

  2. 错误处理复杂

    • 回调函数中的错误处理逻辑需要分散在各个回调函数中,增加了错误处理的复杂性。

    • 例如,每个回调函数都需要单独处理错误。

  3. 调试困难

    • 回调函数的调用链可能很长,调试时难以跟踪回调函数的执行顺序和上下文。

3.回调函应用

1. Linux 信号捕捉

在 Linux 系统中,信号(Signal)是一种软件中断机制,用于通知进程发生了某些事件。进程可以通过信号处理函数(即回调函数)来响应这些事件。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

// 定义信号处理函数(回调函数)
void signal_handler(int sig) {
    printf("Received signal %d\n", sig);
}

int main() {
    // 注册信号处理函数
    signal(SIGINT, signal_handler);  // 捕捉 Ctrl+C 信号
    signal(SIGTERM, signal_handler); // 捕捉 kill 信号

    printf("Process ID: %d\n", getpid());
    printf("Press Ctrl+C to send SIGINT signal\n");

    // 无限循环,等待信号
    while (1) {
        sleep(1);
    }

    return 0;
}
  1. 定义回调函数signal_handler 是一个回调函数,用于处理信号事件。它接收一个参数 sig,表示接收到的信号编号。

  2. 注册回调函数:使用 signal 函数将特定信号(如 SIGINTSIGTERM)与回调函数关联起来。当进程接收到这些信号时,系统会自动调用注册的回调函数。

  3. 触发回调:当进程接收到指定的信号时,操作系统会中断当前执行的代码,调用注册的回调函数。回调函数执行完成后,进程继续执行。

2. 多线程线程创建

在多线程编程中,线程创建时通常需要指定一个回调函数,该函数在线程启动时被调用。

#include <iostream>
#include <thread>

// 定义线程回调函数
void thread_function(int id) {
    std::cout << "Thread " << id << " is running\n";
}

int main() {
    // 创建线程并指定回调函数
    std::thread t1(thread_function, 1);
    std::thread t2(thread_function, 2);

    // 等待线程完成
    t1.join();
    t2.join();

    return 0;
}
  1. 定义回调函数thread_function 是一个回调函数,用于在线程启动时执行。它接收一个参数 id,表示线程的编号。

  2. 创建线程并注册回调函数:使用 std::thread 构造函数创建线程时,将回调函数和参数传递给线程对象。线程启动时会自动调用该回调函数。

  3. 触发回调:线程启动时,系统会调用注册的回调函数,并将线程参数传递给它。

3. Qt 信号槽机制

Qt 是一个跨平台的 C++ 应用程序开发框架,广泛用于开发图形用户界面(GUI)应用程序。Qt 的信号槽机制是一种基于回调的事件处理机制。

#include <QApplication>
#include <QPushButton>

// 定义槽函数(回调函数)
void buttonClicked() {
    std::cout << "Button clicked!\n";
}

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QPushButton button("Click Me");
    button.show();

    // 连接信号和槽
    QObject::connect(&button, &QPushButton::clicked, buttonClicked);

    return app.exec();
}
  1. 定义槽函数buttonClicked 是一个槽函数(回调函数),用于处理按钮点击事件。

  2. 连接信号和槽:使用 QObject::connect 函数将按钮的 clicked 信号与槽函数关联起来。当按钮被点击时,系统会自动调用槽函数。

  3. 触发回调:当用户点击按钮时,按钮会发出 clicked 信号,Qt 的事件循环会检测到该信号并调用注册的槽函数。

4. 可调用对象包装器绑定器

在 C++ 中,可以使用 std::functionstd::bind 来创建可调用对象包装器和绑定器,从而实现更灵活的回调机制。(不需要函数指针,将函数包装成可调用对象)

#include <iostream>
#include <functional>
#include <thread>

// 定义回调函数
void callback_function(int a, int b) {
    std::cout << "Callback called with arguments: " << a << " and " << b << std::endl;
}

int main() {
    // 使用 std::bind 绑定回调函数和参数
    auto bound_callback = std::bind(callback_function, 10, 20);

    // 使用 std::function 包装回调函数
    std::function<void()> callback = bound_callback;

    // 在线程中调用回调函数
    std::thread t(callback);
    t.join();

    return 0;
}
  1. 定义回调函数callback_function 是一个回调函数,接收两个参数 ab

  2. 绑定回调函数和参数:使用 std::bind 将回调函数和参数绑定在一起,生成一个可调用对象。

  3. 包装回调函数:使用 std::function 将绑定后的可调用对象包装起来,使其可以作为回调函数使用。

  4. 触发回调:在新线程中调用包装后的回调函数。

2.Reactor 模型

Reactor模型是一种事件驱动的设计模式,用于处理多个并发I/O请求,核心思想是"非阻塞I/O + 多路复用"

Reactor 模型的核心思想是将事件处理逻辑与事件监听逻辑分离,使得系统能够高效地处理大量并发事件。

1.核心组件

  1. Reactor(反应器):

    • 事件循环核心,负责监听和分发事件

    • 通过系统调用(如select/poll/epoll)监控多个文件描述符

  2. Handlers(事件处理器):

    • 具体事件的处理逻辑

    • 通常包括:连接建立、数据读取、数据写入等

  3. Demultiplexer(多路分解器):

    • 底层I/O多路复用实现(如Linux的epoll)

    • 等待事件发生并通知Reactor

2.工作流程

  1. 注册事件:将需要监控的事件注册到Demultiplexer

  2. 事件循环:Reactor启动事件循环,调用Demultiplexer等待事件

  3. 事件触发:当某个文件描述符就绪时,Demultiplexer返回就绪事件

  4. 分发处理:Reactor将事件分发给对应的Handler处理

  5. 回调执行:Handler执行具体的业务逻辑

+-------------------+       +-------------------+
|     Reactor       |<----->|  Demultiplexer    |
|   (事件分发中心)   |       | (select/epoll等)  |
+-------------------+       +-------------------+
        ^
        | 分发事件
        v
+-------------------+
|     Handlers      |
| (具体事件处理器)   |
+-------------------+

3.Reactor 模型的优点

  1. 高并发处理能力

    • 通过事件驱动和回调机制,Reactor 模型可以高效地处理大量并发事件,而无需为每个连接创建一个线程或进程。

    • 特别适合处理高并发的网络服务,如 Web 服务器、聊天服务器等。

  2. 资源利用率高

    • 事件分发器使用 I/O 多路复用机制,可以同时监听多个文件描述符,减少了系统资源的浪费。

    • 事件处理器通常是非阻塞的,不会阻塞事件循环,从而提高了系统的响应速度和吞吐量。

  3. 代码结构清晰

    • Reactor 模型将事件处理逻辑与事件监听逻辑分离,使得代码结构更加清晰,易于维护和扩展。

    • 事件处理器可以独立开发和测试,提高了代码的可复用性。

4.Reactor 模型的缺点

  1. 回调地狱

    • 由于事件处理逻辑是通过回调函数实现的,当事件处理逻辑复杂时,可能会导致回调嵌套过深,形成“回调地狱”,使代码难以理解和维护。

    • 例如,多个异步操作的嵌套回调可能会导致代码结构混乱。

  2. 错误处理复杂

    • 在事件驱动的编程模型中,错误处理逻辑需要分散在各个事件处理器中,增加了错误处理的复杂性。

    • 例如,网络错误、文件读写错误等需要在每个事件处理器中单独处理。

  3. 不适合 CPU 密集型任务

    • Reactor 模型主要适用于 I/O 密集型任务,对于 CPU 密集型任务,事件循环可能会被阻塞,从而影响系统的性能。

    • 例如,计算密集型任务可能会导致事件循环无法及时处理新的事件。

5.Reactor 模型的应用场景

Reactor 模型广泛应用于高性能网络编程和并发处理场景,以下是一些典型的应用场景:

  1. Web 服务器

    • Web 服务器需要同时处理大量客户端的 HTTP 请求,Reactor 模型可以高效地处理并发请求,提高服务器的吞吐量。

    • 例如,Nginx 和 Node.js 都采用了类似的事件驱动模型。

  2. 聊天服务器

    • 聊天服务器需要实时处理用户的连接和消息,Reactor 模型可以快速响应用户的输入和消息推送。

    • 例如,基于 WebSocket 的聊天服务可以利用 Reactor 模型实现高效的通信。

  3. 游戏服务器

    • 游戏服务器需要处理大量玩家的实时交互,Reactor 模型可以高效地处理玩家的输入和游戏逻辑。

    • 例如,多人在线游戏服务器可以利用 Reactor 模型实现高并发处理。

  4. 分布式系统

    • 分布式系统中的节点需要高效地通信和同步数据,Reactor 模型可以用于实现高效的网络通信。

    • 例如,分布式缓存系统和分布式数据库可以利用 Reactor 模型实现高效的节点间通信。

6.Reactor 模型的实现示例

以下是一个简单的 Reactor 模型实现示例,使用 C++ 和 epoll 机制:

#include <iostream>
#include <vector>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>

// 设置文件描述符为非阻塞模式
void setNonBlocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(fd, F_SETFL, flags);
}

// 事件处理器接口
class EventHandler {
public:
    virtual void handleEvent(int fd) = 0;
    virtual ~EventHandler() {}
};

// Reactor 类
class Reactor {
public:
    Reactor() : epoll_fd(epoll_create1(0)) {}

    ~Reactor() {
        close(epoll_fd);
    }

    // 注册事件处理器
    void registerHandler(int fd, EventHandler* handler) {
        setNonBlocking(fd);
        struct epoll_event ev;
        ev.events = EPOLLIN | EPOLLET; // 边缘触发,监听读事件
        ev.data.ptr = handler;
        epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
    }

    // 运行事件循环
    void run() {
        std::vector<struct epoll_event> events(16);
        while (true) {
            int nfds = epoll_wait(epoll_fd, events.data(), events.size(), -1);
            for (int i = 0; i < nfds; ++i) {
                EventHandler* handler = static_cast<EventHandler*>(events[i].data.ptr);
                handler->handleEvent(events[i].data.fd);
            }
        }
    }

private:
    int epoll_fd;
};

// 示例事件处理器
class MyEventHandler : public EventHandler {
public:
    void handleEvent(int fd) override {
        std::cout << "Event on fd: " << fd << std::endl;
        // 处理事件逻辑,例如读取数据
    }
};

int main() {
    Reactor reactor;
    int fd = 0; // 示例文件描述符,实际应用中可以是套接字
    MyEventHandler handler;
    reactor.registerHandler(fd, &handler);
    reactor.run();
    return 0;
}

在这个示例中,Reactor 类负责管理事件循环和事件分发器(epoll),EventHandler 是事件处理器的接口,MyEventHandler 是具体的事件处理器实现。通过调用 registerHandler 方法,可以将文件描述符和事件处理器注册到 Reactor 中,然后运行事件循环来处理事件。

3.为什么Reactor适合高并发

  1. 非阻塞I/O+多路复用

    • 通过epoll/kqueue/select等系统调用单线程监控上万连接

    • 示例:Linux epoll的O(1)时间复杂度事件通知

  2. 事件驱动架构

    • 只有活跃连接才会消耗CPU资源

    • 与线程池模型对比:

      模型10k空闲连接内存开销10k活跃连接CPU负载
      线程池800MB+100%
      Reactor<100MB根据实际流量波动
  3. 避免线程上下文切换

    • 单Reactor线程处理网络I/O时:

    • 对比传统每连接每线程模型:

      上下文切换次数 = 0(Reactor) vs N*2(线程模型,N=连接数)

 2.Moduo网络库简介

  • 开发背景:muduo是陈硕个人开发的TCP网络编程库,主要用于Linux平台。

  • 编程模型:它基于Reactor模式,支持多线程。其核心设计是一个线程一个事件循环(one loop per thread),即一个线程只能有一个事件循环(EventLoop),而一个文件描述符(fd)只能由一个线程进行读写。

  • 主要特性

    • 使用非阻塞IO和事件驱动。

    • 提供了线程池(ThreadPool)来处理耗时的计算任务。

    • 支持TCP连接的生命周期管理。

    • 提供了丰富的回调机制,用于处理连接建立、消息到达、连接断开等事件。

  • 适用场景:适用于开发高并发的TCP服务器,将网络I/O与业务代码分开

muduo的主要组件

  • EventLoop:事件循环,负责监听事件并调用对应的回调函数。

  • Poller:事件监听器,负责检测事件是否触发,muduo默认使用epoll实现。

  • TcpServer:用于创建TCP服务器,管理连接和事件循环。

  • TcpConnection:表示一个TCP连接,封装了连接的读写操作。

  • Acceptor:负责接收新连接,并将其分配到子事件循环(subReactor)中。

muduo的优势

  • 高性能:基于Linux的epoll机制,能够高效地处理大量并发连接。

  • 易用性:提供了简洁的API,方便开发者快速实现网络服务。

  • 可扩展性:支持多线程模型,可以根据CPU核心数灵活扩展。

    [主Reactor线程]
           |
           | accept()
           ↓
    [ 连接分配器 ] ← Round-Robin/哈希
           |
           ↓
    [从Reactor线程1] [从Reactor线程2] ... [从Reactor线程N]
       |       |          |       |           |       |
     read() write()    read() write()      read() write()

 1.Muduo 的特点

1.基于 Reactor 模型

  • 采用 epoll(多路复用)实现事件驱动的 I/O 处理。

  • 高效的事件循环(EventLoop)避免了传统阻塞式编程的缺陷。

2.多线程友好

  • 采用 one loop per thread(每个线程一个 EventLoop)的方式,避免锁争用,提高并发性能。

  • 提供 EventLoopThreadPool,支持多线程调度。

3.非阻塞 + 事件驱动

  • 采用 非阻塞套接字 + 边缘触发(ET 模式),降低 CPU 负担,提高吞吐量。

  • 配合定时器(TimerQueue)支持高精度定时任务。

在网络服务器开发中,经常需要 定时任务,比如:

  • 定期清理空闲连接(避免资源浪费)

  • 定时发送心跳包(保持连接活跃)

  • 任务超时检测(如 HTTP 请求超时)

TimerQueue 主要功能

  1. 支持 一次性定时任务(只执行一次)

  2. 支持 周期性定时任务(定期触发)

  3. 使用 timerfd 触发定时事件,精度高

  4. 所有定时任务都在 EventLoop 线程中执行线程安全

4.现代 C++ 设计

  • 遵循 RAII 原则,避免资源泄露。

  • 使用 boost::noncopyable 防止对象拷贝,提高安全性。

  • 提供智能指针(如 std::shared_ptr)管理连接生命周期,避免悬空指针问题。

5.高效的 Buffer 设计 

  • Buffer 类封装了 零拷贝 机制,提高数据读写性能。

零拷贝机制是一种优化技术,旨在减少数据在内存中的拷贝次数,从而提高数据传输的效率。在传统的数据传输过程中,数据通常需要在多个缓冲区之间进行多次拷贝,这不仅增加了 CPU 的负担,还可能导致性能瓶颈。零拷贝机制通过减少这些不必要的拷贝操作,显著提高了数据传输的性能。

1.传统数据传输方式

在传统的数据传输中,数据通常需要经过多次拷贝。例如,从磁盘读取数据并发送到网络,可能涉及以下步骤:

  1. 从磁盘读取数据到内核缓冲区

    • 数据从磁盘读取到内核的缓冲区。

    • 这一步通常由 read 系统调用完成。

  2. 从内核缓冲区拷贝到用户空间缓冲区

    • 数据从内核缓冲区拷贝到用户空间的缓冲区。

    • 这一步通常由 read 系统调用完成。

  3. 从用户空间缓冲区拷贝到内核缓冲区

    • 数据从用户空间的缓冲区拷贝回内核缓冲区。

    • 这一步通常由 write 系统调用完成。

  4. 从内核缓冲区发送到网络

    • 数据从内核缓冲区发送到网络。

    • 这一步通常由 write 系统调用完成。

这种传统的数据传输方式涉及多次数据拷贝,增加了 CPU 的负担,降低了性能。

2.零拷贝机制的优势

零拷贝机制通过减少数据在内存中的拷贝次数,显著提高了数据传输的效率。具体优势包括:

  1. 减少 CPU 负担

    • 减少了数据在内核空间和用户空间之间的拷贝操作,降低了 CPU 的使用率。

  2. 提高吞吐量

    • 减少了数据传输的延迟,提高了系统的吞吐量。

  3. 减少内存使用

    • 减少了不必要的内存分配和释放,提高了内存的使用效率。

3.零拷贝机制的实现方式
1. 使用 mmap 和 sendfile

mmapsendfile 是 Linux 提供的两种零拷贝机制,它们可以显著减少数据在内存中的拷贝次数。

  • mmap

    • mmap 将文件映射到用户空间的内存中,允许直接访问文件内容,而无需通过 read 系统调用。

    • 数据可以直接从用户空间的内存发送到网络,减少了内核空间和用户空间之间的拷贝。

  • sendfile

    • sendfile 是一个系统调用,允许直接从一个文件描述符(通常是文件)发送数据到另一个文件描述符(通常是套接字)。

    • 数据直接从内核缓冲区发送到网络,无需经过用户空间,减少了数据拷贝。

2. 使用 splice

splice 是另一种零拷贝机制,允许在两个文件描述符之间直接传输数据,而无需经过用户空间。splice 可以用于管道(pipe)和套接字之间的数据传输。

3. 使用 writev 和 readv

writevreadv 是 Linux 提供的 I/O 系统调用,允许将多个缓冲区的数据一次性写入或读取,减少了系统调用的次数。

  • writev

    • 允许将多个缓冲区的数据一次性写入文件描述符。

    • 数据在内核空间中直接拼接,减少了用户空间和内核空间之间的拷贝。

  • readv

    • 允许从文件描述符一次性读取数据到多个缓冲区。

    • 数据在内核空间中直接拆分,减少了用户空间和内核空间之间的拷贝。

4.muduo 中的零拷贝机制

muduo 网络库通过以下方式实现了零拷贝机制:

  1. 使用 writevreadv

    • muduoBuffer 类在读取和写入数据时,使用 readvwritev 系统调用,减少了数据在内核空间和用户空间之间的拷贝。

  • 支持 预留空间,减少内存分配次数。

1. 预留空间(Prepend Space)

muduo::Buffer 类在设计上预留了一段空间(kCheapPrepend),通常为 8 字节。这段预留空间位于缓冲区的开头,用于优化小数据写入操作。

  • 优化小数据写入:当需要写入少量数据时,可以直接利用预留空间,避免了内存移动操作。

  • 减少内存分配次数:通过预留空间,可以在不频繁扩容的情况下,高效地处理小数据的写入。

2. 动态扩容策略

muduo::Buffer 类支持动态扩容,当可写空间不足时,会根据当前数据量的大小动态调整缓冲区的大小。这种策略考虑了整个缓冲区的使用情况,包括预留空间和已读数据区域,从而避免了不必要的内存分配。

  • 扩容条件:当可写空间不足时,Buffer 会根据当前数据量的大小动态调整缓冲区的大小。

  • 扩容策略:扩容时,Buffer 会考虑预留空间和已读数据区域,从而避免了不必要的内存分配。

3. 内存分配优化

通过预留空间和动态扩容策略,muduo::Buffer 类显著减少了内存分配次数,从而提高了性能。

  • 减少内存分配次数:通过预留空间和动态扩容策略,减少了不必要的内存分配。

  • 提高性能:减少了内存分配次数,从而提高了数据读写的性能。

6.支持 TCP 服务器 & 客户端

  • 封装了 TcpServerTcpClient,简化开发,降低网络编程的复杂度。

7.集成日志系统

  • 提供高效的日志模块 muduo::Logger,支持日志等级、文件输出等。

muduo 网络库提供了一个高效且功能丰富的日志模块 muduo::Logger,支持日志等级、文件输出、异步日志记录等功能。以下是其主要特点和使用方法:

1. 日志等级

muduo::Logger 提供了多种日志级别,包括 TRACEDEBUGINFOWARNERRORFATAL。这些级别允许开发者根据日志的重要性进行筛选,从而在不同环境下控制日志输出的详细程度。

2. 异步日志记录

muduo 的日志系统采用异步日志记录机制,通过 AsyncLogging 类实现。这种方式可以有效防止日志输出阻塞程序的正常运行。AsyncLogging 使用独立的线程进行日志写入,主线程只需将日志消息放入队列,由后台线程负责写入磁盘。

3. 文件输出与滚动策略

muduo 支持将日志输出到文件,并提供了日志文件滚动功能。这包括基于文件大小和时间的滚动策略:

  • 基于大小的滚动:当日志文件达到指定大小时,会自动创建新的日志文件。

  • 基于时间的滚动:支持按天滚动日志文件,每天生成一个新的日志文件。

4. 日志格式与自定义输出

muduo::Logger 支持自定义日志格式,开发者可以根据需要配置日志的输出格式。此外,还可以通过设置输出函数,将日志输出到控制台、文件或其他目标。

2.Muduo 的 one loop per thread 

  • Main Reactor(主Reactor) 只负责接收新连接,不处理数据读写。

  • Sub Reactors(子Reactor 线程池) 处理已建立连接的读写、回调、计算等任务

  • 每个线程一个 EventLoop(one loop per thread),保证 Reactor 线程间互不干扰

1. 什么是 poll 的大小?

这里的 poll 指的是 epoll 的监听对象数,即每个 EventLoop 线程最多能管理的连接数

  • poll(或 epoll_wait)用来监听 socket 事件,比如可读、可写、新连接等。

  • 每个 Reactor(线程)都有一个 epoll 实例,监听其负责的所有连接。

  • “大小固定” 指的是:在 one loop per thread 设计下,每个线程的 epoll 管理的连接数受限于 CPU 资源

2. 为什么要根据 CPU 数目确定?

Muduo 采用 多线程 + 负载均衡 来充分利用 CPU 资源:

  • 一台服务器的 CPU 核心数 = 最佳 Sub Reactor 线程数

  • 每个 CPU 核心运行一个 Sub Reactor 线程,即每个 CPU 只运行一个 epoll,避免线程间竞争,提高性能。

这样做的好处是:

  1. 最大化 CPU 利用率:每个 CPU 只运行一个 Reactor 线程,避免线程争用 CPU 资源。

  2. 减少锁竞争:每个 EventLoop 线程独立运行,不需要锁来同步访问共享资源。

  3. 负载均衡:新的连接采用 Round-Robin(轮询)方式分配给 Reactor 线程,均匀分布负载。

如果服务器有 4 核 CPU,Muduo 的线程模型如下:

1 个主 Reactor 线程MainReactor)—— 负责 accept() 接收新连接,并分发给 Sub Reactor 线程。

4 个子 Reactor 线程SubReactors)—— 处理 I/O 事件(read/write)。

业务线程池(可选)—— 处理耗时任务,如数据库操作、计算等(避免阻塞 Sub Reactor 线程)

3.Moduo使用

1.tcp

(1)client

#pragma once

#include <muduo/base/Logging.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpClient.h>
#include <thread>

namespace dfs {
class HeartbeatClient {
public:
  HeartbeatClient(muduo::net::EventLoop *loop, std::string &host, int port,
                  int id);    // 构造函数
  void start();               // 开始
  void stop();                // 结束

private:
                              // *loop *connection *client
  muduo::net::EventLoop *loop_;
  muduo::net::TcpConnectionPtr conn_;
  std::unique_ptr<muduo::net::TcpClient> client_;

};
} // namespace dfs
1.std::unique_ptr<muduo::net::TcpClient> 而不是直接使用 TcpClient 对象:
1. 生命周期控制(核心原因)
  • 问题场景:当需要在某个条件(如连接失败)后销毁 TcpClient 时,只能通过指针实现。

2. 异步操作的安全性
  • muduo 的网络操作都是异步的(如 connect()),可能在对象销毁后仍有未完成的回调

2.muduo 是事件驱动的,鼓励所有操作都通过事件回调来驱动 
#include "heartbeat_client.h"

namespace dfs {
HeartbeatClient::HeartbeatClient(muduo::net::EventLoop *loop, std::string &host,
                                 int port, int id)
    : loop_(loop), host_(host), port_(port), id_(id), running_(true) {}

void HeartbeatClient::start() {
  muduo::net::InetAddress serverAddr(host_, port_);

  // 1.client赋值
  client_ = std::make_unique<muduo::net::TcpClient>(loop_, serverAddr,
                                                    "HeartbeatClient");
 // 2.client回调
  client_->setConnectionCallback(
      [this](const muduo::net::TcpConnectionPtr &conn) {
        if (conn->connected()) {
          conn_ = conn;
          LOG_INFO << "Connected to server >>>";
        } else {
          LOG_ERROR << "Heartbeat failed >>>";
          conn_.reset();
        }
      });

 // 3.client连接
  client_->connect();

// 4.client发送
  running_ = true;
  heartbeat_thread_ = std::thread([&]() {
    while (running_) {
      if (conn_ && conn_->connected()) {
        std::string msg = std::to_string(id_);
        conn_->send(msg);
        LOG_INFO << "Send heartbeat";
      }
      std::this_thread::sleep_for(std::chrono::seconds(3));
    }
  });
}

void HeartbeatClient::stop() {
  running_ = false;
  if (heartbeat_thread_.joinable()) {
    heartbeat_thread_.join();
  }
}

} // namespace dfs

 EventLoop 的设计原则是 每个线程最多有一个 EventLoop 对象,并且所有与该 EventLoop 相关的操作(如 loop()quit()runInLoop() 等)都必须在创建它的线程中执行。如果在其他线程中调用了这些操作,就会触发 abortNotInLoopThread 函数,导致程序终止

EventLoop::runInLoopmuduo 网络库中 EventLoop 类的一个成员函数,用于将一个回调函数提交到 EventLoop 所在的线程中执行。这个函数的主要目的是确保某些操作在 EventLoop 线程中安全地执行,从而避免线程安全问题。

  • 如果当前线程就是 EventLoop 所在的线程,runInLoop 会直接调用回调函数。

  • 如果当前线程不是 EventLoop 所在的线程,runInLoop 会将回调函数放入 EventLoop 的任务队列中,然后在 EventLoop 的主循环中执行这些任务。

#include "heartbeat_client.h"
#include <muduo/net/EventLoop.h>
#include <thread>

int main() {
  muduo::net::EventLoop loop;

  std::string ip = "127.0.0.1";
  dfs::HeartbeatClient client(&loop, ip, 9000, 1);

  // 在子线程中启动心跳发送逻辑
  std::thread heartbeatThread([&client]() {
    client.start();
    std::this_thread::sleep_for(std::chrono::seconds(20));
    client.stop();
  });

  loop.loop();  // ✅ EventLoop 一定要在它**创建的线程中运行**

  heartbeatThread.join();
  return 0;
}

 (2)server

#pragma once

#include <muduo/base/Logging.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpServer.h>
#include <thread>

#include "nodeManager.h"

namespace dfs {
class HeartbeatServer {
public:
  HeartbeatServer(muduo::net::EventLoop *loop, int addr, NodeManager *manager);//构造
  void start();// 开始

private:
  void onConnect(const muduo::net::TcpConnectionPtr &conn);    //连接
  void onMessage(const muduo::net::TcpConnectionPtr &conn,     //发送消息
                 muduo::net::Buffer *buffer, muduo::Timestamp time);

  muduo::net::TcpServer server_;
};
} // namespace dfs
#include "heartbeat_server.h"

namespace dfs {
HeartbeatServer::HeartbeatServer(muduo::net::EventLoop *loop, int addr,
                                 NodeManager *manager)
    : server_(loop, muduo::net::InetAddress(addr), "HeartbeatServer"),
      manager_(manager) {
  server_.setConnectionCallback(
      std::bind(&HeartbeatServer::onConnect, this, std::placeholders::_1));
  server_.setMessageCallback(
      std::bind(&HeartbeatServer::onMessage, this, std::placeholders::_1,
                std::placeholders::_2, std::placeholders::_3));
}

void HeartbeatServer::start() { server_.start(); }

void HeartbeatServer::onConnect(const muduo::net::TcpConnectionPtr &conn) {
  LOG_INFO << "Heartbeat Connection from " << conn->peerAddress().toIpPort();
}

void HeartbeatServer::onMessage(const muduo::net::TcpConnectionPtr &conn,
                                muduo::net::Buffer *buffer,
                                muduo::Timestamp time) {
  std::string msg = buffer->retrieveAllAsString();
  if (msg.empty()) {
    LOG_WARN << "Empty heartbeat from " << conn->peerAddress().toIpPort();
    return;
  }
  int node_id = std::stoi(msg);
  manager_->updateHeartbeat(node_id);
}
} // namespace dfs

1. conn->peerAddress()

conn->peerAddress()muduo::net::TcpConnection 类的一个成员函数,返回一个 muduo::net::InetAddress 对象,表示对端的网络地址。

2. toIpPort()

toIpPort()muduo::net::InetAddress 类的一个成员函数,将 IP 地址和端口号格式化为一个字符串,格式为 "IP:Port"

#include "heartbeat_server.h"

#include <muduo/net/EventLoop.h>

int main() {
  muduo::net::EventLoop loop;
 
  dfs::HeartbeatServer server(&loop, 9000, &manager);

  server.start();
  loop.loop();

  return 0;
}
1.小规模服务:单线程 Reactor,所有 I/O 和计算在一个线程内完成。
#include <functional> // 包含 std::bind 和 std::placeholders 等功能
#include <iostream>   // 包含标准输入输出流
#include <muduo/base/Logging.h>  // 包含 Muduo 的日志模块
#include <muduo/net/EventLoop.h> // 包含事件循环类
#include <muduo/net/TcpServer.h> // 包含 TCP 服务器类
#include <string>                // 包含标准字符串类

namespace muduo {
namespace net {

// 定义一个 EchoServer 类,用于创建回声服务器
class EchoServer {
public:
  // 构造函数,初始化服务器
  EchoServer(muduo::net::EventLoop *loop,         // 事件循环对象
             const muduo::net::InetAddress &addr, // 服务器地址和端口
             const std::string &name)             // 服务器名称
      : server_(loop, addr, name),                // 初始化 TcpServer 对象
        loop_(loop) // 保存事件循环对象的指针
  {
    // 注册连接回调函数
    // 当有新的客户端连接或断开连接时,调用 EchoServer::OnConnect 函数
    server_.setConnectionCallback(
        std::bind(&EchoServer::OnConnect, this, std::placeholders::_1));

    // 注册消息回调函数
    // 当接收到客户端发送的消息时,调用 EchoServer::OnMessage 函数
    server_.setMessageCallback(
        std::bind(&EchoServer::OnMessage, this, std::placeholders::_1,
                  std::placeholders::_2, std::placeholders::_3));
  }

  // 启动服务器
  void start() {
    server_.start(); // 调用 TcpServer 的 start 方法启动服务器
  }

private:
  // 消息回调函数
  void OnMessage(const muduo::net::TcpConnectionPtr &conn, // 客户端连接对象
                 muduo::net::Buffer *buff,                 // 消息缓冲区
                 muduo::Timestamp time) // 消息到达的时间
  {
    std::string str = buff->retrieveAllAsString(); // 从缓冲区中读取消息
    conn->send(str);  // 将消息原样返回给客户端
    conn->shutdown(); // 关闭连接的写端,表示不再发送数据
  }

  // 连接回调函数
  void OnConnect(const muduo::net::TcpConnectionPtr &conn) {
    if (conn->connected()) {
      // 如果客户端连接成功,打印客户端和服务器的地址信息
      std::cout << conn->peerAddress().toIpPort() // 客户端地址和端口
                << " -> " << conn->localAddress().toIpPort() // 服务器地址和端口
                << " state: online" << std::endl;
    } else {
      // 如果客户端断开连接,打印相关信息
      std::cout << conn->peerAddress().toIpPort() // 客户端地址和端口
                << " -> " << conn->localAddress().toIpPort() // 服务器地址和端口
                << " state: offline" << std::endl;
      conn->shutdown(); // 关闭连接
    }
  }

  muduo::net::EventLoop *loop_;  // 事件循环对象指针
  muduo::net::TcpServer server_; // TCP 服务器对象
};

} // namespace net
} // namespace muduo

int main() {
  muduo::net::EventLoop loop;                      // 创建事件循环对象
  muduo::net::InetAddress addr("127.0.0.1", 8080); // 定义服务器地址和端口
  muduo::net::EchoServer server(&loop, addr,
                                "EchoServer"); // 创建 EchoServer 对象

  server.start(); // 启动服务器,监听新连接,并注册事件回调
  loop.loop();    // 进入 Reactor 事件循环,不断监听和处理事件

  return 0;
}

std::bind创建一个“绑定器”,将一个函数(或成员函数)和它的参数绑定在一起,形成一个新的可调用对象

#include <muduo/net/TcpClient.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>
#include <muduo/net/Buffer.h>
#include <iostream>
#include <string>

using namespace muduo;
using namespace muduo::net;

class EchoClient {
public:
    EchoClient(EventLoop* loop, const InetAddress& serverAddr)
        : loop_(loop),
          client_(loop, serverAddr, "EchoClient"),
          connectionEstablished_(false) {
        // 设置连接回调函数
        client_.setConnectionCallback(std::bind(&EchoClient::onConnection, this, std::placeholders::_1));

        // 设置消息回调函数
        client_.setMessageCallback(std::bind(&EchoClient::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    }

    void connect() {
        client_.connect();
    }

    void send(const std::string& message) {
        if (connectionEstablished_) {
            conn_->send(message);
        } else {
            std::cerr << "Connection not established yet." << std::endl;
        }
    }

private:
    void onConnection(const TcpConnectionPtr& conn) {
        conn_ = conn;
        if (conn->connected()) {
            connectionEstablished_ = true;
            std::cout << "Connected to server." << std::endl;
        } else {
            connectionEstablished_ = false;
            std::cout << "Disconnected from server." << std::endl;
        }
    }

    void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) {
        std::string message = buf->retrieveAllAsString();
        std::cout << "Received message from server: " << message << std::endl;
    }

    EventLoop* loop_;
    TcpClient client_;
    TcpConnectionPtr conn_;
    bool connectionEstablished_;
};

int main() {
    EventLoop loop;
    InetAddress serverAddr("127.0.0.1", 8080); // 服务器地址和端口
    EchoClient client(&loop, serverAddr);

    client.connect(); // 连接到服务器

    // 发送一条消息到服务器
    client.send("Hello, server!");

    loop.loop(); // 开始事件循环,让客户端开始运行

    return 0;
}
2.大规模服务:多线程 Reactor,主线程负责 I/O,工作线程池处理业务逻辑
#include <iostream>
#include <muduo/base/Logging.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>
#include <muduo/net/TcpServer.h>
#include <string>

using namespace muduo;
using namespace muduo::net;

class EchoServer {
public:
  EchoServer(EventLoop *loop, const InetAddress &listenAddr, int numThreads)
      : server_(loop, listenAddr, "EchoServer") {
    // 设置线程池的线程数量
    server_.setThreadNum(numThreads);

    // 设置连接回调函数
    server_.setConnectionCallback(
        std::bind(&EchoServer::onConnection, this, std::placeholders::_1));

    // 设置消息回调函数
    server_.setMessageCallback(
        std::bind(&EchoServer::onMessage, this, std::placeholders::_1,
                  std::placeholders::_2, std::placeholders::_3));
  }

  void start() { server_.start(); }

private:
  void onConnection(const TcpConnectionPtr &conn) {
    if (conn->connected()) {
      LOG_INFO << "Connection UP: " << conn->peerAddress().toIpPort();
    } else {
      LOG_INFO << "Connection DOWN: " << conn->peerAddress().toIpPort();
    }
  }

  void onMessage(const TcpConnectionPtr &conn, Buffer *buf,
                 Timestamp receiveTime) {
    std::string msg(buf->retrieveAllAsString());
    LOG_INFO << "Received message from " << conn->peerAddress().toIpPort()
             << ": " << msg;
    conn->send(msg); // 回声
  }

  TcpServer server_;
};

int main(int argc, char *argv[]) {
  LOG_INFO << "pid = " << getpid();
  if (argc > 1) {
    EventLoop loop;
    InetAddress listenAddr(8080);
    int numThreads = argc > 2 ? atoi(argv[2]) : 3; // 默认使用 3 个线程
    EchoServer server(&loop, listenAddr, numThreads);
    server.start();
    loop.loop();
  } else {
    std::cerr << "Usage: " << argv[0] << " <port> [num_threads]" << std::endl;
  }
  return 0;
}
3.日志

Muduo 日志模块支持多种日志级别,每种级别用于记录不同严重程度的信息。以下是 Muduo 日志模块支持的日志级别及其用途:

1. TRACE
  • 用途:记录最详细的信息,通常用于开发和调试阶段。

  • LOG_TRACE << "This is a trace message.";
2. DEBUG
  • 用途:记录调试信息,用于开发和调试阶段。

  • LOG_DEBUG << "This is a debug message.";
3. INFO
  • 用途:记录正常运行时的重要信息。

  • LOG_INFO << "This is an info message.";
4. WARN
  • 用途:记录警告信息,表示程序可能存在问题,但不会影响正常运行。

  • LOG_WARN << "This is a warning message.";
5. ERROR
  • 用途:记录错误信息,表示程序运行中出现了严重问题。

  • LOG_ERROR << "This is an error message.";
6. FATAL
  • 用途:记录致命错误,通常会导致程序崩溃。

  • LOG_FATAL << "This is a fatal error message.";

日志级别从低到高依次为:

TRACE < DEBUG < INFO < WARN < ERROR < FATAL

配置日志级别

你可以通过配置文件或环境变量来设置日志的最低输出级别。例如,如果你只想输出 INFO 及以上级别的日志,可以设置日志级别为 INFO

2.http

Muduo 是一个基于 C++ 的高性能网络库,广泛用于构建高并发、低延迟的网络应用。其 HttpServer 是一个基于 TcpServer 的 HTTP 服务框架,能够高效处理 HTTP 请求和响应。以下是 muduo::http::HttpServer 的简单介绍和使用方法:

  1. 创建 EventLoop 和 Thread Pool

    • EventLoop 是 Muduo 的事件循环,负责监听和分发 I/O 事件。

    • ThreadPool 用于处理业务逻辑,提高并发处理能力。

    cpp复制

    muduo::net::EventLoop loop;
    muduo::net::ThreadPool threadPool(&loop, "http");
    threadPool.start(4); // 启动 4 个工作线程
  2. 初始化 HttpServer

    • 创建 HttpServer 对象,并绑定到 EventLoopThreadPool

    cpp复制

    HttpServer(EventLoop* loop,
               const InetAddress& listenAddr,
               const std::string& name,
               TcpServer::Option option = TcpServer::kNoReusePort);
  3. 注册请求处理回调函数

    • 使用 HttpServer::setHttpCallback 注册回调函数,用于处理 HTTP 请求并生成响应。

    cpp复制

    httpServer.setHttpCallback([](const muduo::net::HttpRequest& req, muduo::net::HttpResponse* resp) {
        // 根据请求内容处理逻辑
        resp->setStatusCode(muduo::net::HttpResponse::k200Ok);
        resp->setStatusMessage("OK");
        resp->setContentType("text/html");
        resp->setBody("<html><body><h1>Hello, world!</h1></body></html>");
    });
  4. 启动服务器

    • 调用 EventLoop::loop 启动事件循环,开始监听和处理 HTTP 请求。

    cpp复制

    httpServer.start();
    loop.loop();

核心组件

  1. HttpRequest:封装 HTTP 请求的细节,包括方法(如 GET、POST)、URL、头部信息和请求体。

  2. HttpResponse:管理 HTTP 响应的构建,包括状态码、头部和响应体。

  3. HttpContext:管理 HTTP 请求的解析状态,并存储 HttpRequest 对象,确保请求的完整性和一致性。

  4. HttpServer:核心组件,负责接受连接、读取请求和发送响应,基于 Muduo 的事件驱动架构实现高并发和低延迟处理。

#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>
#include <muduo/net/http/HttpRequest.h>
#include <muduo/net/http/HttpResponse.h>
#include <muduo/net/http/HttpServer.h>
#include <iostream>

using namespace muduo;
using namespace muduo::net;

class UploadServer {
public:
  UploadServer(EventLoop* loop, const InetAddress& listenAddr)
    : server_(loop, listenAddr, "UploadServer") {
    server_.setHttpCallback(std::bind(&UploadServer::onRequest, this,
                                      std::placeholders::_1,
                                      std::placeholders::_2));
  }

  void start() {
    server_.start();
  }

private:
  void onRequest(const HttpRequest& req, HttpResponse* resp) {
    if (req.method() == HttpRequest::Method::kPost && req.path() == "/upload") {
      std::string body = req.body();
      std::cout << "Received upload data: " << body << std::endl;
      // 你可以在这里调用 FileManager::saveFile 处理数据
      resp->setStatusCode(HttpResponse::k200Ok);
      resp->setContentType("text/plain");
      resp->setBody("Upload received.");
    } else {
      resp->setStatusCode(HttpResponse::k404NotFound);
      resp->setContentType("text/plain");
      resp->setBody("Not Found");
    }
  }

  HttpServer server_;
};

int main() {
  EventLoop loop;
  InetAddress listenAddr(8080);
  UploadServer server(&loop, listenAddr);
  server.start();
  loop.loop();
}

 3.CMake

add_executable(server server.cpp)

# 链接Muduo库
target_link_libraries(server 
    "/usr/local/lib/libmuduo_net.a" 
    "/usr/local/lib/libmuduo_base.a"
)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值