C++Muduo网络库初探

news2024/11/26 12:00:57

Muduo初探

Muduo网络库简介

Muduo是由【陈硕】大佬个人开发的TCP网络编程库,基于Reactor模式,提供了高效的事件驱动网络编程框架,有助于快速搭建高性能的网络服务端。

什么是Reactor模式?

I/O多路复用

在网络I/O中,如果队每个连接都用一个独立的线程来处理,会导致大量的线程资源消耗。因此,出现了一种能够使用一个线程来监听所有网络连接的I/O事件的方法 —— I/O多路复用

在这里插入图片描述

常见的I/O复用方法:select、poll、epoll。其中,epoll是一种事件驱动的I/O多路复用的方法。

在这里插入图片描述

事件驱动的核心是,以事件为连接点,当有IO事件准备就绪时,以事件的形式通知相关线程进行数据读写,进而业务线程可以直接处理这些数据,这一过程的后续操作方,都是被动接收通知,看起来有点像回调操作。

这种模式下,I/O 读写线程、业务线程工作时,必有数据可操作执行,不会在 I/O 等待上浪费资源,这便是事件驱动的核心思想。

Reactor模型

Reactor 是事件驱动模型的一种实现

Reactor 模式由 Reactor 线程Handlers 处理器两大角色组成,两大角色的职责分别如下:

  • Reactor 线程的职责:主要负责连接建立、监听IO事件、IO事件读写以及将事件分发到Handlers 处理器。
  • Handlers 处理器(业务处理)的职责:非阻塞的执行业务处理逻辑。

在这里插入图片描述

对于Reactor模型,将建立连接、IO等待/读写以及事件转发等操作分阶段处理,对于不同阶段采用响应的优化策略来提高性能,常见的有下列三种:

  • 单线程模型
  • 多线程模型(Worker线程池)
  • 主从多线程模型

单线程Reactor模型

在这里插入图片描述

在单线程 Reactor 模式中,ReactorHandler 都在同一条线程中执行。所有 I/O 操作(包括连接建立、数据读写、事件分发等)、业务处理,都是由一个线程完成的,逻辑非常简单,但也有十分明显的缺陷:

  • 一个线程支持处理的连接数非常有限,CPU 很容易打满,性能方面有明显瓶颈

  • 当多个事件被同时触发时,只要有一个事件没有处理完,其他后面的事件就无法执行,这就会造成消息积压及请求超时

  • 线程在处理 I/O 事件时,Select 无法同时处理连接建立、事件分发等操作

  • 如果 I/O 线程一直处于满负荷状态,很可能造成服务端节点不可用

当其中某个 Handler 阻塞时,会导致其他所有的 Handler 都得不到执行。

在这种场景下,被阻塞的 Handler 不仅仅负责输入和输出处理的传输处理器,还包括负责新连接监听的 Acceptor 处理器,可能导致服务器无响应。这是一个非常严重的缺陷,导致单线程反应器模型在生产场景中使用得比较少。

多线程Reactor模型(Worker线程池)

在这里插入图片描述

Reactor 多线程模型业务逻辑交给多个线程进行处理。除此之外,多线程模型其他的操作与单线程模型是类似的,比如连接建立、IO事件读写以及事件分发等都是由一个线程来完成。

当客户端有数据发送至服务端时,Select 会监听到可读事件,数据读取完毕后提交到业务线程池中并发处理。一般的请求中,耗时最长的一般是业务处理,所以用一个线程池(worker 线程池)来处理业务操作,在性能上的提升也是非常可观的。

当然,这种模型也有明显缺点,连接建立、IO 事件读取以及事件分发完全有单线程处理;比如当**某个连接通过系统调用正在读取数据,此时相对于其他事件来说,完全是阻塞状态,新连接无法处理、其他连接的 IO、查询 IO 读写以及事件分发都无法完成**。对于像 Nginx、Netty 这种对高性能、高并发要求极高的网络框架,这种模式便显得有些吃力了。因为,无法及时处理新连接、就绪的 IO 事件以及事件转发等。

主从多线程Reactor模型

img

主从 Reactor 模式中,分为了**主 Reactor** 和 从 Reactor,分别处理 新建立的连接IO读写事件/事件分发

  1. 主 Reactor 可以解决同一时间大量新连接,将其注册到从 Reactor 上进行IO事件监听处理
  2. IO事件监听相对新连接处理更加耗时,此处我们可以考虑使用线程池来处理。这样能充分利用多核 CPU 的特性,能使更多就绪的IO事件及时处理。

主从多线程模型由多个 Reactor 线程组成,每个 Reactor 线程都有独立的 Selector 对象。MainReactor 仅负责处理客户端连接的 Accept 事件,连接建立成功后将新创建的连接对象注册至 SubReactor。再由 SubReactor 分配线程池中的 I/O 线程与其连接绑定,它将负责连接生命周期内所有的 I/O 事件

在海量客户端并发请求的场景下,主从多线程模式甚至可以适当增加 SubReactor 线程的数量,从而利用多核能力提升系统的吞吐量。****

总结

Reactor核心是围绕事件驱动模型

  • 一方面监听并处理IO事件
  • 另一方面将这些处理好的事件分发业务线程处理

多线程模式(多线程模式和主从多线程模式),其工作模式大致如下:

  • 将负责数据传输处理的 IOHandler 处理器的执行放入独立的线程池中。这样,业务处理线程与负责新连接监听的反应器线程就能相互隔离,避免服务器的连接监听受到阻塞。
  • 如果服务器为多核的 CPU,可以将反应器线程拆分为多个子反应器(SubReactor)线程;同时,引入多个选择器,并且为每一个SubReactor引入一个线程,一个线程负责一个选择器的事件轮询。这样充分释放了系统资源的能力,也大大提升了反应器管理大量连接或者监听大量传输通道的能力。

Reactor(反应器)模式高性能网络编程在设计和架构层面的基础模式,算是基础的原理性知识。只有彻底了解反应器的原理,才能真正构建好高性能的网络应

Muduo基本架构

采用了**主从多线程reactor模型 + 线程池的架构。Main Reactor只用于监听新的连接,在accept之后就会将这个连接分配到Sub Reactor上,由子Reactor负责连接的事件处理。线程池中维护了两个队列,一个队伍队列**,一个线程队列,外部线程将任务添加到任务队列中,如果线程队列非空,则会唤醒其中一只线程进行任务的处理,相当于是生产者和消费者模型

在这里插入图片描述

Muduo基本代码结构

muduo库有三个核心组件支撑一个Reactor实现持续的监听一组fd,并根据每个fd上发生的事件调用相应的处理函数。这三个组件分别是Channel类Poller/EpollPoller类以及EventLoop类

EventLoop起到一个驱动循环的功能,Poller负责从事件监听器上获取监听结果。而Channel类则在其中起到了将fd及其相关属性封装的作用,将fd及其感兴趣事件和发生的事件以及不同事件对应的回调函数封装在一起,这样在各个模块中传递更加方便。接着EventLoop调用。

image

EventLoop类

在Muduo网络库中,EventLoop类是核心组件之一。它的作用是**提供事件循环机制,**负责管理和调度各种事件(如I/O事件、定时器事件),确保这些事件能够及时被处理,从而实现高效的网络通信。

EventLoop的工作流程大致如下:

  1. 创建一个EventLoop对象。
  2. 将需要监控的文件描述符和对应的事件类型注册到EventLoop中。
  3. 启动事件循环,进入循环体。
  4. 在循环体中,使用epoll等系统调用等待事件的发生。
  5. 当有事件发生时,调用相应的回调函数来处理这些事件。
  6. 重复步骤4和5,直到事件循环停止。

主要属性

looping_:标志事件循环是否正在运行。
quit_:标志是否需要退出事件循环。
poller_:指向Poller对象的指针,Poller封装了具体的I/O多路复用机制(如epoll)。
activeChannels_:存储当前活跃的Channel对象列表。
currentActiveChannel_:当前正在处理的Channel对象。
eventHandling_:标志是否正在处理事件。
callingPendingFunctors_:标志是否正在调用待处理的任务。
mutex_:用于保护任务队列的互斥锁。
pendingFunctors_:待处理任务的队列。
threadId_:运行该EventLoop的线程ID。
timerQueue_:定时器队列,用于管理定时器事件。

主要方法

loop(): 启动事件循环。该方法会进入循环体,不断地等待并处理事件,直到调用quit()方法。
    
quit():  退出事件循环。该方法会设置quit_标志,在安全的时机退出事件循环。
    
runInLoop(Functor cb): 在当前事件循环中执行指定的任务。如果在其他线程中调用该方法,会将任务添加到pendingFunctors_队列中。
    
queueInLoop(Functor cb):将任务添加到待处理任务队列中,在事件循环的下一次迭代时执行。
    
updateChannel(Channel channel): 更新一个Channel,即在Poller中更新该Channel所关注的事件。
removeChannel(Channel channel):移除一个Channel,即在Poller中取消对该Channel的关注。
hasChannel(Channel channel):检查Poller中是否包含指定的Channel。

wakeup():  唤醒事件循环,使其从阻塞状态中退出。通常用于在其他线程中添加任务后通知事件循环执行。

doPendingFunctors():执行待处理任务队列中的所有任务。该方法在事件循环中调用,以确保所有任务都能在事件循环中安全地执行。

handleRead():处理读事件,通常用于处理唤醒事件循环的事件。
handleError():处理错误事件。

事件循环工作流程

启动事件循环

  • 调用loop()方法,进入事件循环。

等待事件

  • 调用Pollerpoll()方法等待事件发生。

处理事件

  • 遍历活跃的Channel列表,调用每个Channel的事件处理回调函数。

处理定时器事件

  • 检查并处理到期的定时器事件。

执行待处理任务

  • 调用doPendingFunctors()方法,执行在其他线程中添加的任务。

重复循环

  • 重复上述步骤,直到调用quit()方法退出事件循环。

Poller类

Poller类是EventLoop的一个重要组件,负责具体的I/O多路复用机制的封装。Poller类是一个抽象基类,它提供了统一的接口,以便不同的具体实现(如使用epollpoll)可以继承并实现这些接口(对应两个派生类:PollPoller和EpollPoller)。具体使用中会根据环境变量的设置选择是使用epoll还是poll方法。Poller类的主要作用是管理和分发I/O事件

主要属性

ownerLoop_:指向所属的EventLoop对象。确保Poller在正确的事件循环线程中被使用。
channels_:保存文件描述符到Channel对象的映射,便于快速查找。

主要方法

virtual Timestamp poll(int timeoutMs, ChannelList activeChannels) = 0*:   纯虚函数,等待事件发生并将活跃的Channel填充到activeChannels列
表中。timeoutMs指定等待事件的超时时间。

virtual void updateChannel(Channel channel) = 0*:纯虚函数,更新指定的Channel。通常涉及将该Channel的文件描述符和事件类型添加或修改到多路复用机制中。

virtual void removeChannel(Channel channel) = 0*:  纯虚函数,从多路复用机制中移除指定的Channel。

bool hasChannel(Channel channel) const*: 检查Poller中是否包含指定的Channel。通过查找channels_映射实现。

主要工作流程

创建并初始化Poller

  • 在创建EventLoop对象时,会根据系统和配置选择具体的Poller实现(如EPollPollerPollPoller)。

添加、更新和移除Channel

  • 通过调用updateChannelremoveChannel方法,管理Channel对象及其关注的事件。

等待事件

  • 在事件循环中,调用poll方法等待事件发生,并获取活跃的Channel列表。

分发事件

  • 将活跃的事件分发给相应的Channel对象,Channel对象再调用预先设置的回调函数处理事件。

Channel类

在 Muduo 网络库中,Channel 类是一个关键组件,用于表示和管理文件描述符及其相关的 I/O 事件。它负责将底层的 I/O 事件和应用层的回调函数关联起来,使得事件处理更加抽象和灵活。

主要属性

loop_: 指向所属的 EventLoop 对象,确保 Channel 所属的事件循环。

fd_:文件描述符,表示这个 Channel 关注的具体文件描述符。

events_:表示当前 Channel 感兴趣的事件,由位掩码表示,如 EPOLLIN, EPOLLOUT 等。

revents_:表示实际发生的事件,由 EventLoop 设置。

index_:用于在 Poller 中记录 Channel 的状态(是否在 epoll 或 poll 的关注列表中)。

tied_:表示是否与某个对象(如 TcpConnection)绑定,防止对象在事件处理过程中被销毁。

eventHandling_:标志当前是否正在处理事件。

addedToLoop_:标志当前 Channel 是否已经添加到事件循环中。

readCallback_、writeCallback_、errorCallback_、closeCallback_:一系列的回调函数,用于处理不同类型的事件。

主要方法

  1. void handleEvent(Timestamp receiveTime);

    用途:处理发生的事件,根据 revents_ 的值调用相应的回调函数。
    参数:receiveTime,表示事件发生的时间戳。
    实现逻辑:根据 revents_ 的值,依次调用错误、关闭、读、写事件的回调函数。
    
  2. setReadCallback:设置读事件回调函数;setWriteCallback:设置写事件回调函数;setErrorCallback:设置错误事件回调函数;setCloseCallback:设置关闭事件回调函数。

    void setReadCallback(ReadEventCallback cb) { readCallback_ = std::move(cb); }   // 设置读事件回调函数。
    void setWriteCallback(EventCallback cb) { writeCallback_ = std::move(cb); }   // 设置写事件回调函数。
    void setErrorCallback(EventCallback cb) { errorCallback_ = std::move(cb); }
    void setCloseCallback(EventCallback cb) { closeCallback_ = std::move(cb); }
    用途:将用户定义的回调函数绑定到相应的事件上。
    参数:回调函数对象,使用 std::function 封装。
    
  3. 事件启用/禁用

    void enableReading() { events_ |= kReadEvent; update(); }
    void enableWriting() { events_ |= kWriteEvent; update(); }
    void disableReading() { events_ &= ~kReadEvent; update(); }
    void disableWriting() { events_ &= ~kWriteEvent; update(); }
    void disableAll() { events_ = kNoneEvent; update(); }
    用途:启用或禁用 Channel 对特定事件的监听。
    实现逻辑:修改 events_ 的值,并调用 update() 方法将修改通知给 Poller。
    
  4. update()

    用途:将 Channel 的状态更新到 Poller 中。
    实现逻辑:调用 EventLoop 的 updateChannel 方法,将当前 Channel 的状态同步到 Poller。
    void Channel::update() {
        addedToLoop_ = true;
        loop_->updateChannel(this);
    }
    
  5. remove()

    用途:从 Poller 中移除当前 Channel。
    实现逻辑:调用 EventLoop 的 removeChannel 方法,从 Poller 中删除当前 Channel。
    void Channel::remove() {
        addedToLoop_ = false;
        loop_->removeChannel(this);
    }
    
  6. tie()

    void tie(const std::shared_ptr<void>& obj);
    用途:将 Channel 绑定到一个对象(通常是 TcpConnection),防止在处理事件时该对象被销毁。
    参数:一个 std::shared_ptr<void> 对象。
    实现逻辑:存储 weak_ptr,在处理事件时检查对象是否有效。
    void Channel::tie(const std::shared_ptr<void>& obj) {
        tie_ = obj;
        tied_ = true;
    }
    
  7. 其他辅助方法

    int fd() const { return fd_; }    // 返回文件描述符。
    int events() const { return events_; }   // 返回当前感兴趣的事件。
    void set_revents(int revt) { revents_ = revt; } // 设置实际发生的事件。
    bool isNoneEvent() const { return events_ == kNoneEvent; }  // 检查是否没有感兴趣的事件。
    

TcpConnection类

在 Muduo 网络库中,TcpConnection 类是一个重要的组件,表示一个已建立的TCP连接和控制该TCP连接的方法(连接建立和关闭和销毁),以及这个TCP连接的服务端和客户端的套接字地址信息等。它封装了 socket 文件描述符,并提供了处理连接的各种事件和操作的功能。TcpConnection 类主要负责管理连接的生命周期、数据的读写、回调函数的调用等。

主要属性

loop_:所属的 EventLoop 对象,表示这个连接归属于哪个事件循环。
name_:连接的名称,通常用来唯一标识一个连接。
state_:连接的状态,如连接建立、连接关闭等。
channel_:对应的 Channel 对象,用于管理 socket 的事件。
socket_:封装的 socket 文件描述符。
inputBuffer_:这是一个Buffer类,是该TCP连接对应的用户接收缓冲区。
outputBuffer_:也是一个Buffer类,不过是用于暂存那些暂时发送不出去的待发送数据. 因为Tcp发送缓冲区是有大小限制的,假如达到了高水位线,就没办法把发送的数据通过send()直接拷贝到Tcp发送缓冲区,而是暂存在这个outputBuffer_中,等TCP发送缓冲区有空间了,触发可写事件了,再把outputBuffer_中的数据拷贝到Tcp发送缓冲区中。
connetionCallback_、messageCallback_、writeCompleteCallback_、closeCallback_:用户会自定义 [连接建立/关闭后的处理函数][收到消息后的处理函数][消息发送完后的处理函数]以及Muduo库中定义的[连接关闭后的处理函数]。这四个函数都会分别注册给这四个成员变量保存。

主要方法

  1. 内部事件处理:handleRead()、handleWrite()、handleClose()、handleError()

    私有成员方法,在一个已经建立好的TCP连接上主要会发生四类事件:可读事件、可写事件、连接关闭事件、错误事件。当事件监听器监听到一个连接发生了以上的事件,那么就会在EventLoop中调用这些事件对应的处理函数,同时accept返回已连接套接字所绑定的Channel中注册了这四种回调函数

    void handleRead(Timestamp receiveTime);
    void handleWrite();
    void handleClose();
    void handleError();
    // 处理读、写、关闭和错误事件,由 Channel 对象调用。
    
  2. 连接状态的获取和设置

    const std::string& name() const { return name_; }
    const InetAddress& localAddress() const { return localAddr_; }
    const InetAddress& peerAddress() const { return peerAddr_; }
    bool connected() const { return state_ == kConnected; }
    // 获取连接的名称、本地地址、对端地址和连接状态。
    
  3. 回调函数的设置

    void setConnectionCallback(const ConnectionCallback& cb) { connectionCallback_ = cb; }
    void setMessageCallback(const MessageCallback& cb) { messageCallback_ = cb; }
    void setWriteCompleteCallback(const WriteCompleteCallback& cb) { writeCompleteCallback_ = cb; }
    void setCloseCallback(const CloseCallback& cb) { closeCallback_ = cb; }
    // 设置连接、消息、写入完成和关闭的回调函数。
    
  4. 发送数据

    void send(const std::string& message);
    void send(Buffer* buf);
    // 发送数据,将数据添加到输出缓冲区并调用 `handleWrite` 方法进行实际发送。
    
  5. 关闭连接

    void shutdown();
    // 关闭写端,触发连接关闭过程。
    
  6. 强制关闭

    void forceClose();
    // 强制关闭连接,不等待缓冲区的数据发送完毕。
    
  7. 连接建立和销毁

    void connectEstablished();
    void connectDestroyed();
    // 连接建立时的初始化操作和连接销毁时的清理操作。
    

Acceptor类

Acceptor 类在 Muduo 网络库中是一个关键组件,主要负责接受新的客户端连接并将其传递给用户定义的回调函数Acceptor 封装了底层的监听 socketChannel,并提供了对新连接的处理逻辑。

主要属性

loop_:所属的 EventLoop 对象,表示这个 Acceptor 归属于哪个事件循环。
acceptSocket_:监听 socket,封装了底层的 socket 文件描述符。
acceptChannel_:用于监听事件的 Channel 对象。
listenning_:表示是否处于监听状态。
idleFd_:预留的文件描述符,用于处理文件描述符耗尽的情况。
newConnectionCallback_:新连接到达时的回调函数。

主要方法

  1. 监听

    void listen();
    用途:启动监听 socket 开始接受新的连接。
    实现逻辑:调用 Socket 对象的 listen 方法,并将 acceptChannel 设置为可读状态以处理新的连接。
    
  2. 设置回调函数

    void setNewConnectionCallback(const NewConnectionCallback& cb) { newConnectionCallback_ = cb; }
    用途:设置新连接到达时的回调函数。
    
  3. 处理新连接

    void handleRead();
    用途:处理新的连接请求,接受连接并调用用户定义的回调函数。
    实现逻辑:调用 Socket 对象的 accept 方法获取新的连接文件描述符,并调用 newConnectionCallback_ 处理新连接。
    

TcpServer类

TcpServer 类在 Muduo 网络库中是一个高层次的网络服务器类,负责管理多个 TCP 连接,并提供了一些便捷的方法来设置各种回调函数和启动服务器。它结合了 AcceptorTcpConnection 等类来处理新的连接和数据传输。

主要属性

loop_:主 EventLoop 对象,用于处理主线程中的事件。
ipPort_:服务器的 IP 和端口。
name_:服务器名称。
acceptor_:Acceptor 对象,负责接受新连接。
threadPool_:用于处理多个 EventLoop 线程的线程池。
connectionCallback_:连接建立或关闭时的回调函数。
messageCallback_:消息到达时的回调函数。
writeCompleteCallback_:数据写入完成时的回调函数。
connections_:管理所有活动连接的容器。

主要方法

void start();  // 启动服务器

// 设置回调函数
void setConnectionCallback(const ConnectionCallback& cb) { connectionCallback_ = cb; }
void setMessageCallback(const MessageCallback& cb) { messageCallback_ = cb; }
void setWriteCompleteCallback(const WriteCompleteCallback& cb) { writeCompleteCallback_ = cb; }

void setThreadNum(int numThreads);  // 设置线程数量


void newConnection(int sockfd, const InetAddress& peerAddr); // 处理新连接

// 移除连接
void removeConnection(const TcpConnectionPtr& conn);
void removeConnectionInLoop(const TcpConnectionPtr& conn);

Muduo简单使用

使用muduo库编写一个简单的echo回显服务器

// 测试muduo库代码
// 使用muduo库实现一个简单的echo回显服务器
// ~ by magic_pri 2024.6.15

#include <muduo/net/TcpServer.h>
#include <muduo/base/Logging.h>
#include <boost/bind/bind.hpp>
#include <muduo/net/EventLoop.h>

// 使用muduo开发回显服务器
class EchoServer{
public:
    // 构造函数,输入参数
    // loop:EentLoop类指针,用于事件循环
    // listenAddr: InetAddress类对象,服务端的地址结构
    EchoServer(muduo::net::EventLoop* loop, const muduo::net::InetAddress& listenAddr);    
    
    void start();

private:

    // 连接建立或关闭时的回调函数
    void onConnection(const muduo::net::TcpConnectionPtr& conn);

    // 收到消息时的回调函数
    void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp time);

    muduo::net::TcpServer server_;  // 私有成员变量:TcpServer类对象,管理所有连接
};

// 构造函数实现:初始化TcpServer对象并设置回调函数
EchoServer::EchoServer(muduo::net::EventLoop* loop, 
                        const muduo::net::InetAddress& listenAddr)
                        : server_(loop, listenAddr, "EchoServer")   // 列表初始化成员变量
{   
    // 使用boost::bind绑定回调函数
    // [this]是捕获当前'EchoServer'对象的this指针,以便可以访问其成员函数
    // _1, _2, _3为占位符,表示回调函数应当传入的几个参数
    server_.setConnectionCallback(boost::bind(&EchoServer::onConnection, this, boost::placeholders::_1)); // 绑定建立或关闭连接时的回调函数
    server_.setMessageCallback(boost::bind(&EchoServer::onMessage, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3));   // 绑定获取消息的回调函数
}

// start的实现:启动服务器,开始监听端口并接受新的连接
void EchoServer::start(){
    server_.start();
}

// 回调函数的实现
void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn){
    // LOG_INFO,muduo库中的打印日志方法
    LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "
             << conn->localAddress().toIpPort() << " is "
             << (conn->connected() ? "UP" : "DOWN");
}

void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, 
                            muduo::Timestamp time){
    // 接收到所有消息,然后回显
    muduo::string msg(buf->retrieveAllAsString());  // 将缓冲区消息提取成muduo::string

    // 输出日志,记录接收到的消息
    LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, " 
             << "data received at " << time.toString();

    // 回显,发送给对端(服务端发送给客户端,客户端发送给服务端)
    conn->send(msg);
}

int main(){                            
    // 打印日志,输出当前进程的PID
    LOG_INFO << "pid = " << getpid();

    // 创建循环事件对象
    muduo::net::EventLoop loop;

    // 创建InetAdress对象,表示服务器监听的地址和端口
    muduo::net::InetAddress listenAdrr(8888);

    // 创建EchoServer对象
    EchoServer server(&loop, listenAdrr);

    // 启动服务器,开始监听
    server.start();

    // 进入事件循环
    loop.loop();
}

问题解答

  1. 代码如何运行?

    # Step1:编译
    g++ main.cpp -lmuduo_net -lmuduo_base -lpthread -std=c++11 -o EchoServer
    
    # Step2:运行
    ./EchoServer
    # 运行成功后显示
    # 20240618 13::44:45.347367Z 34919 INFO  pid = 34919 - main.cpp:72
    
    # Step3:客户端访问
    # 新建一个终端窗口,访问本机器8888端口号
    echo "hello world" | ns localhost 8888
    
  2. loop.loop()事件循环的作用

    如果不调用 loop.loop(),程序会在 server.start() 之后立即退出,服务器将无法接收和处理客户端连接。loop.loop() 是事件驱动服务器程序的核心,它使得程序进入事件循环,能够持续处理网络事件,保持服务器运行和响应客户端请求。没有这个调用,服务器将无法正常运行。

    Muduo 库中的 EventLoop 类封装了 I/O 多路复用和事件处理机制,提供了更高层次的接口。loop.loop() 实现了一个高效的事件循环,具备以下优势:

    • 高效的事件等待和处理:使用 epoll 或其他高效的 I/O 多路复用机制来等待事件,并在事件发生时高效处理。
    • 自动管理回调函数EventLoop 自动管理回调函数的注册和调用,确保在正确的时间处理正确的事件。
    • 支持定时器和其他任务:除了 I/O 事件,EventLoop 还支持定时器事件和其他需要在特定时间点处理的任务。
    • 更清晰的代码结构:通过使用事件循环库,可以将事件等待和处理逻辑从业务逻辑中分离,使代码结构更清晰、维护更方便。

参考博客

本文部分内容/图片摘抄自以下博客,如有侵权,请告知删除:

高性能网络编程之 Reactor 网络模型(彻底搞懂)_reactor网络模型-CSDN博客

长文梳理muduo网络库核心代码、剖析优秀编程细节 - miseryjerry - 博客园 (cnblogs.com)

muduo网络库学习总结:基本架构及流程分析_muduo主从reactor-CSDN博客

长文梳理muduo网络库核心代码、剖析优秀编程细节 - miseryjerry - 博客园 (cnblogs.com)

C++Muduo网络库:简介及使用-CSDN博客

C++ muduo网络库知识分享01 - Linux平台下muduo网络库源码编译安装-CSDN博客

SDN博客](https://blog.csdn.net/ldw201510803006/article/details/124365838)

长文梳理muduo网络库核心代码、剖析优秀编程细节 - miseryjerry - 博客园 (cnblogs.com)

muduo网络库学习总结:基本架构及流程分析_muduo主从reactor-CSDN博客

长文梳理muduo网络库核心代码、剖析优秀编程细节 - miseryjerry - 博客园 (cnblogs.com)

C++Muduo网络库:简介及使用-CSDN博客

C++ muduo网络库知识分享01 - Linux平台下muduo网络库源码编译安装-CSDN博客

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

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

相关文章

eclipse中没有SERVER的解决办法(超详细)

将 Tomcat 和 Eclipse 相关联时&#xff0c;Eclipse有的版本发现 发现eclipse->【Window】->【Preferences】里没有【server】从而配置不了Runtime Environment。所以需要通过eclipse进行安装。 通过我个人的经验下面给出解决办法&#xff1a; 一、获取 Eclipse版本 点击…

性能测试并发量评估新思考:微服务压力测试并发估算

性能测试并发量评估新思考 相信很多人在第一次做压力测试的时候&#xff0c;对并发用户数的选择一直有很多的疑惑&#xff0c;那么行业内有一些比较通用的并发量的计算方法&#xff0c;但是这些方法在如今微服务的架构下多少会有一些不适合&#xff0c;下面的文章我们对这些问题…

JS【数组】交集、差集、补集、并集

var a [1,2,3,4,5] var b [2,4,6,8,10]var sa new Set(a); var sb new Set(b); // 交集 let intersect a.filter(x > sb.has(x)); // 差集 let minus a.filter(x > !sb.has(x)); // 补集 let complement [...a.filter(x > !sb.has(x)), ...b.filter(x > !sa…

MicroBlaze IP核中Local Memory Bus (LMB)接口描述

LMB&#xff08;Local Memory Bus&#xff09;是一种同步总线&#xff0c;主要用于访问FPGA上的块RAM&#xff08;Block RAM&#xff0c;BRAM&#xff09;。LMB使用最少的控制信号和一个简单的协议&#xff0c;以保证块RAM能在一个时钟周期内被存取。所有的LMB信号都是高电平有…

[深度学习]--分类问题的排查错误的流程

原因复现&#xff1a; 原生的.pt 好使&#xff0c; 转化后的 CoreML不好使&#xff0c; 分类有问题。 yolov8 格式的支持情况 Format Argument Suffix CPU GPU 0 PyTorch - .pt True True 1 Tor…

Unet已死,Transformer当立!详细解读基于DiT的开源视频生成大模型EasyAnimate

Diffusion Models视频生成-博客汇总 前言&#xff1a;最近阿里云PIA团队开源了基于Diffusion Transformer结构的视频生成模型EasyAnimate&#xff0c;并且提出了专门针对视频的slice VAE&#xff0c;对于目前基于Unet结构的视频生成最好如SVD形成了降维打击&#xff0c;不论是生…

快消品经销商的仓库管理,有哪些是必须注意的事项?

快消品经销商仓库管理是一个复杂而关键的过程&#xff0c;它涉及到产品的存储、保管、发货以及库存控制等多个环节。一个高效的仓库管理系统不仅有助于减少成本&#xff0c;提高运营效率&#xff0c;还能确保产品质量和满足客户需求。以下是一些快消品经销商在仓库管理过程中需…

JDBC之API(DriverManager)详解

之前在 JDBC 的快速入门写代码的时候&#xff0c;遇到了很多的API。这篇博客主要学习一些API。 目录 一、API&#xff08;介绍&#xff09; 二、JDBC之API——DriverManager &#xff08;1&#xff09;DriverManager &#xff08;获取 Connection 的连接对象&#xff09; 1、…

玩转热门游戏,选对系统是关键!游戏专用电脑系统在这里!

如果我们给电脑安装上游戏专用系统&#xff0c;那么就能体验到更加流畅、稳定的游戏运行环境&#xff0c;享受沉浸式的游戏体验。但是&#xff0c;许多新手用户不知道去哪里才能下载到玩游戏专用的电脑系统&#xff1f;接下来小编给大家分享玩游戏专用电脑系统&#xff0c;这些…

Windows应急响应靶机 - Web2

一、靶机介绍 应急响应靶机训练-Web2 前景需要&#xff1a;小李在某单位驻场值守&#xff0c;深夜12点&#xff0c;甲方已经回家了&#xff0c;小李刚偷偷摸鱼后&#xff0c;发现安全设备有告警&#xff0c;于是立刻停掉了机器开始排查。 这是他的服务器系统&#xff0c;请你…

【YOLOv5/v7改进系列】引入特征融合网络——ASFYOLO

一、导言 ASF-YOLO结合空间和尺度特征以实现精确且快速的细胞实例分割。在YOLO分割框架的基础上&#xff0c;通过引入尺度序列特征融合(SSFF)模块来增强网络的多尺度信息提取能力&#xff0c;并利用三重特征编码器(TFE)模块融合不同尺度的特征图以增加细节信息。此外&#xff…

尚硅谷大数据技术ClickHouse教程-笔记01【ClickHouse单机安装、数据类型】

视频地址&#xff1a;一套上手ClickHouse-OLAP分析引擎&#xff0c;囊括Prometheus与Grafana_哔哩哔哩_bilibili 01_尚硅谷大数据技术之ClickHouse入门V1.0 尚硅谷大数据技术ClickHouse教程-笔记01【ClickHouse单机安装、数据类型】尚硅谷大数据技术ClickHouse教程-笔记02【表引…

前端基础操作1——利用nvm任意切换(管理)node版本

在实际前端项目开发过程中&#xff0c;同时开发多个项目或者切换新项目时&#xff0c;因为node版本问题造成项目无法运行的问题比比皆是&#xff0c;这时候通过nvm管理切换不同版本的node&#xff0c;就能很快进入开发模式&#xff0c;避免因为环境问题浪费大量精力&#xff0c…

太爱这种数据可视化效果,零售行业的都看过来

在当今数字化浪潮下&#xff0c;数据可视化已成为零售行业洞察市场趋势、优化运营决策的关键技术。奥威BI零售数据分析方案凭借其卓越的数据可视化效果&#xff0c;成为零售企业的得力助手。接下来就通过BI节假日分析报表来简单地感受一下。 注&#xff1a;该BI节假日分析报表…

ArcGIS消除碎图斑

​ 点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 点击学习——>遥感影像综合处理4大遥感软件ArcGISENVIErdaseCognition 这次是上次 我们经常在相交、标识、更新等操作后或者是栅格转矢量可能存在很多的细碎图斑&#…

反激开关电源输出整流二极管选型及计算

1、电压 输出整流二极管电压应力&#xff1a;VoutVin/n尖峰电压余量5373/14.22010&#xff08;选60V左右的二极管&#xff09; 通常为了降低电压尖峰&#xff0c;需要在二极管上加RC吸收&#xff0c;来降低尖峰 2、电流 整流二极管的电流也是需要根据变压器输出绕组的峰值电流来…

深度学习500问——Chapter12:网络搭建及训练(3)

文章目录 12.3.5 Caffe有哪些接口 12.4 网络搭建有什么原则 12.4.1 新手原则 12.4.2 深度优先原则 12.4.3 卷积核size一般为奇数 12.4.4 卷积核不是越大越好 12.5 有哪些经典的网络模型值得我们去学习的 12.6 网络训练有哪些技巧 12.6.1 合适的数据集 12.6.2 合适的预…

console.log日志展示

console.log日志展示 1.特定颜色打印 console.log("%caaa","color:#1e80ff") 解释&#xff1a;重点就是%c和color。

IDEA2023中使用run Dashboard面板?实现批量运行微服务

1、直接点击Add service--->Run Configuration Type---->Spring Boot 2、这样就出现了run Dashboard面板&#xff0c;可同时运行多个工程模块&#xff0c;shift选中所有启动类组命名&#xff08;Group Configurations&#xff09; 3、启动所有的项目

linux分区

挂载只改变数据存储的分区位置&#xff0c;不改变文件系统的目录结构&#xff0c;无论怎么挂载linux系统的分区仍然是/根分区和其子目录/opt、/etc、/home等等 临时挂载 添加新磁盘模拟新设备接入&#xff0c;无论使用什么虚拟机都可以 重启电脑reboot&#xff0c;执行lsblk&a…