[Muduo网络库]:梳理Muduo库核心代码

参考文章:
1.长文梳理Muduo库核心代码及优秀编程细节剖析
2.《Linux多线程服务器编程-使用muduo C++ 网络库》-陈硕

一 前言

代码地址:https://github.com/chenshuo/muduo
Muduo是陈硕个人开发的TCP网络编程,支持Reactor模型。陈硕在《Linux多线程服务器编程》中提到他心中理想的网络库的样子:

1.线程安全,原生支持多核多线程。
2.不考虑可移植性,不支持跨平台,只支持Linux,不支持Windows。
3.主要支持x86-64,兼顾IA32.(实际上muduo也可以运行在ARM上)
4.不支持UDP,只支持TCP。
5.不支持IPV6,只支持IPV4。
6.不考虑公网,只考虑局域网(实际上muduo也可以用在广域网中)
7.只支持一种非使用模型:非阻塞IO+one event loop per thread,不支持阻塞IO
8.API简单易用, 只暴露具体类和标准库里的类。 API不使用nontrivial templates, 也不使用虚函数。
9.只满足常用需求的90%, 不面面俱到, 必要的时候以app来适应lib。
10.只做library, 不做成framework。
11.争取全部代码在5000行以内(不含测试) 。
12.在不增加复杂度的前提下可以支持FreeBSD/Darwin, 方便将来用Mac作为开发用机, 但不为它做性能优化。 也就是说, IO multiplexing使用poll(2)和epoll(4)。
13.以上条件都满足时, 可以考虑搭配Google Protocol Buffers RPC。

one loop per thread:将每个线程与一个独立的事件循环绑定。
要保证one loop per thread,主要满足:
1.一个线程只有一个EventLoop对象
2.不该跨线程调用的函数不会跨线程调用

二 前提知识

2.1 Reactor模型

The reactor design pattern is an event handling pattern for handling service requests
delivered concurrently to a service handler by one or more inputs. The service handler
then demultiplexes the incoming requests and dispatches them synchronously to the
associated request handlers.

Reactor模型本质上是一个基于 事件驱动+非阻塞IO+IO多路复用的模型。
Reactor模型的作用是处理事件请求的事件处理模式,对传入的请求进行解复用,并将其同步分派给的请求处理程序。
Reactor模型的重要组件:Event事件,Reactor反应堆,Demultiplex事件分发器,Eventhandle事件处理器
在这里插入图片描述

  1. Reactor:负责监听和分派事件,充当事件分发器。当某个 I/O 事件发生时,Reactor 会将其分派给相应的处理器。
  2. Event Demultiplexer:事件分发分离器,它是 Reactor 模型的底层依赖,负责等待事件的发生,并在事件发生时通知 Reactor。典型的多路分离器包括 select、poll、epoll
  3. Event Handlers:具体的事件处理程序,它们包含了应用程序的业务逻辑,在 Reactor 调度时执行相应的操作。

Reactor模型主要由Reactor和Handler线程池两个核心部分组成:

  • Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件;
  • Handler线程池负责处理事件,如 read -> 业务逻辑 -> send;

理论上,有4种方案可以选择

  • 单 Reactor 单进程 / 线程;
  • 单 Reactor 多进程 / 线程;
  • 多 Reactor 单进程 / 线程;
  • 多 Reactor多进程 / 线程;

muduo采用的是多reactor多线程模型

2.2 多reactor多线程模型

在这里插入图片描述
mainRactor只处理连接事件,用一个线程来处理就好。处理读写事件的subReactor个数一般和CPU数量相等,一个subReactor对应一个线程
Muduo库有三个核心组件支撑一个reactor实现 [持续] 的 [监听] 一组fd,并根据每个fd上发生的事件 [调用] 相应的处理函数。这三个组件分别是Channel类、Poller/EpollPoller类以及EventLoop类

2.3 muduo的简单使用

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

boost 安装

apt-get install libboost-all-dev

cmake安装

sudo apt-get install cmake

源码安装

git clone https://github.com/chenshuo/muduo
cd muduo
./build.sh install

需要注意的是,build.sh install命令可能并不会将Muduo的头文件和库文件安装到Linux的系统路径下(如/usr/include和/usr/local/lib),而是安装到了Muduo源代码目录下的某个子目录中(如build/release-install-cpp11)。因此,需要将/build/release-install-cpp11下面的include和lib目录下的文件拷贝到系统目录下或者建立软链接

sudo ln -s path/to/build/release-install-cpp11/include/mudo/ /usr/include/muduo
sudo ln -s path/to/build/release-install-cpp11/lib/*.a /usr/lib/

使用手册

1.初始化EventLoop,InetAddress,TcpServer;
2.定义Tcpserver的接受回调函数和event处理回调函数

muduo已经把服务端的TCP连接业务和的用户处理业务分开,用户只需要专注于定义连接事件回调函数和处理可读事件回调函数即可。

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

// 使用muduo开发回显服务器
class EchoServer
{
public:
    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_;
};

EchoServer::EchoServer(muduo::net::EventLoop *loop,
                       const muduo::net::InetAddress &listenAddr)
    : server_(loop, listenAddr, "EchoServer")
{
    server_.setConnectionCallback(
        boost::bind(&EchoServer::onConnection, this, _1));
    server_.setMessageCallback(
        boost::bind(&EchoServer::onMessage, this, _1, _2, _3));

    //设置sub reactor数量
    server_.setThreadNum(3);
}

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

void EchoServer::onConnection(const muduo::net::TcpConnectionPtr &conn)
{
    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());
    LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, "
             << "data received at " << time.toString();
    conn->send(msg);
}

int main()
{
    LOG_INFO << "pid = " << getpid();
    // 这个EventLoop就是main EventLoop,(主reactor)即负责循环事件监听处理新用户连接事件的事件循环器。
    muduo::net::EventLoop loop;
    // InetAddress其实是对socket编程中的sockaddr_in进行封装,使其变为更友好简单的接口而已。
    muduo::net::InetAddress listenAddr(8888);
    // 调用TCPserver
    EchoServer server(&loop, listenAddr);
    // 启动TcpServer服务器
    server.start();
    // 执行EventLoop::loop()函数
    loop.loop();
}

三 muduo代码结构

muduo的头文件明确分为客户可见和客户不可见两类。 以下是安装
之后暴露的头文件和库文件。 对于使用muduo库而言, 只需要掌握5个
关键类: Buffer、 EventLoop、 TcpConnection、 TcpClient、 TcpServer。
在这里插入图片描述

3.1 buffer.cc

数据的读写通过buffer进行,用户代码不需要调用read/write,只需要处理收到的数据和准备好要发送的数据

3.2 InetAddress.cc

InetAddress封装IPv4地址(end point) , 注意, 它不能解析域名,只认IP地址。 因为直接用gethostbyname(3)解析域名会阻塞IO线程。

3.3 EventLoop.cc

EventLoop事件循环(反应器Reactor) , 每个线程只能有一个EventLoop实体, 它负责IO和定时器事件的分派。 Poller作为IO multiplexing

3.4 EventLoopThread.cc

启动一个线程, 在其中运行EventLoop::loop()。

3.5 TcpConnection.cc

整个网络库的核心, 封装一次TCP连接, 注意它不能发起连接。

3.6 TcpClient.cc

用于编写网络客户端, 能发起连接, 并且有重试功能。

3.7 TcpServer.cc

用于编写网络服务器, 接受客户的连接。

TcpConnection的生命期依靠shared_ptr管理(即用户和库共同控制) 。 Buffer的生命期由TcpConnection控制。 其余类的生命期由用户控制。 Buffer和InetAddress具有值语义, 可以拷贝; 其他class都是对象语义, 不可以拷贝。

这是muduo的网络核心库的头文件包含关系, 用户可见的为白底, 用户不可见的为灰底
在这里插入图片描述
以下是内部实现:

3.8 Channel.cc

Channel封装了Event,里面存在fd,events,revents,以及相应的回调函数callbacks,是selectable IO channel, 负责注册与响应IO事件, 注意它不拥有file descriptor。 它是Acceptor、 Connector、 EventLoop、TimerQueue、 TcpConnection的成员, 生命期由后者控制。

3.9 Socket.cc

是一个RAIIhandle, 封装一个filedescriptor, 并在析构时关闭fd。 它是Acceptor、 TcpConnection的成员, 生命期由后者控制。EventLoop、 TimerQueue也拥有fd, 但是不封装为Socket class。

3.10 SocketsOps.cc

封装各种Sockets系统调用。

3.11Poller.cc

是PollPoller和EPollPoller的基类, 采用“电平触发”的语意。它是EventLoop的成员, 生命期由后者控制。

3.12 PollPoller.cc和EPollPoller.cc

封装poll和epoll两种IO multiplexing后端。 poll的存在价值是便于调试, 因为poll调用是上下文无关的, 用strace很容易知道库的行为是否正确。

3.13 Connector.cc

用于发起TCP连接, 它是TcpClient的成员, 生命期由后者控制。

3.14 Acceptor.cc

用于接受TCP连接, 它是TcpServer的成员, 生命期由后者控制。

3.15 TimerQueue.cc

用timerfd实现定时, 这有别于传统的设置poll/epoll_wait的等待时长的办法。 TimerQueue用std::map来管理Timer,常用操作的复杂度是O(logN), N为定时器数目。 它是EventLoop的成
员, 生命期由后者控制。

3.16 EventLoopThreadPool.cc

用于创建IO线程池, 用于把TcpConnection分派到某个EventLoop线程上。 它是TcpServer的成员, 生命期由后者控制。

这是muduo的简化类图, Buffer是TcpConnection的成员
在这里插入图片描述

总结

Muduo库是一个基于采用多Reactor多线程模型实现的TCP网络编程库,EventLoop模块(反应器Reactor)与线程一对一绑定, 实现One Loop Per Thread ,这种运行模式是Muduo库的特色,它充分利用了多核cpu的能力。每一个核的线程负责循环监听一组文件描述符的集合。最核心的三个组件为Channel类、Poller/EpollPoller类以及EventLoop类,这个会放在后面的讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值