# sparkfire **Repository Path**: wbvalid/sparkfire ## Basic Information - **Project Name**: sparkfire - **Description**: No description available - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2021-06-26 - **Last Updated**: 2022-03-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简介 基于Muduo网络库二次开发的C++11网络库,用于个人工作需要及学习需要。 # 项目架构解析 (以下内容转自本人CSDN博客,非抄袭。[Muduo网络库](https://blog.csdn.net/wbvalid/article/details/119057885)) ## 1. Reactor的核心机制:事件分发与事件循环 EventLoop流程简图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/771772fe8d6f436db4f226c2edebd70f.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3didmFsaWQ=,size_16,color_FFFFFF,t_70) ### EventLoop 一个事件循环,注意,**一个创建了EventLoop对象的线程是IO线程** * 主要接口 * loop 死循环,阻塞在Poller的poll函数,等待唤醒 唤醒后执行ChannelList中每个Channel的回调 最后执行任务队列中的Functor * runInLoop 在IO线程中执行用户回调Functor,若调用者非IO线程,则会调用queueInLoop * queueInLoop 当调用者并非当前EventLoop所在线程时,将Functor存入EventLoop的任务队列 从而保证Functor由IO线程执行,这是线程安全的保证之一 * updateChannel与removeChannel 核心中的核心,通过这个公有接口建立起Channel和Poller沟通的桥梁 Channel通过这个接口向Poller注册或者移除自己的fd 实现了Poller和Channel两端的解耦 * 核心实现:handleEvent 遍历所有的activeChannelList_,并依次执行这些Channel中注册的回调函数 这个环节非常非常关键,是一切事件派发机制中回调执行的地方 * 主要成员 * wakeupchannel_ 通过eventfd唤醒的channel EventLoop可以通过这个Channel唤醒自己执行定时任务 * activeChannelList_ 通过一次poll获得的所有发生事件的Channel指针列表 * pendingFunctors_ 所有非IO线程调用的用户回调都会存放在这个队列中,通过mutex互斥量保护 * poller_ 一个多路复用实例 ### Poller I/O多路复用接口(抽象)类,对I/O多路复用API的封装 * 主要接口 * poll 是Poller的核心功能,使用派生类的poll或者epoll_wait来阻塞等待IO事件发生 通过派生类的实现来填充EventLoop的activeChannelList_ * static createNewPoller: 工厂函数,创建一个Poller实例 在EpollPoller中,每个实例对应一个epollfd * update 更新I/O多路复用的状态,例如epoll_ctl的ADD,MOD,DEL * 主要成员 * loop 控制当前Poller的EventLoop指针 * 其余成员由派生类实现 ### Channel I/O事件派发器,封装了发生I/O事件的文件描述符,关心的事件类型,以及事件对应的回调 * 主要接口 * setter,getter 设置关注读写操作,获取实际发生的读写事件 * update 根据回调的执行情况,通过EventLoop的updateChannel接口更新事件状态 * 主要成员 * loop Channel拥有者的EventLoop指针 每个EventLoop可以控制若干个Channel * fd_ 这个Channel所维护的文件描述符 * events_ 这个Channel所关心的IO事件 * revents_ 这个Channel的文件描述符上实际发生的事情 ### TimerQueue 通过timerfd实现的定时器功能,为EventLoop扩展了一系列runAt,runEvery,runEvery等函数 TimerQueue中通过std::set维护所有的Timer,也可以使用优先队列实现 * 主要接口 * addTimer 向定时器中添加Timer Timer是一个封装了回调函数和时间的类 通过内部实现addTimerInLoop保证线程安全 * cancel 从定时器中移除某个Timer * 核心实现:getExpired 从timers_集合中移除已经到期的Timer * 核心实现:handleRead 向timerfdChannel注册的回调函数 在timerfd触发可读时间时,也就是定时器到期的时候执行 会调用getExpired,并依次执行返回的所有Timer中的回调 * 主要成员 * timerfdChannel_ 用来唤醒计时器的Channel,维护了一个timerfd,注册了TimerQueue::handleRead回调 * std::set> timers_ 使用std::set容器来取得最近将要超时的Timer,从而决定是否resetTimerfd TimerQueue执行用户回调的时序图: ![TimerQueue时序图](https://img-blog.csdnimg.cn/db14c1dad62f4509b4f440222b000167.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3didmFsaWQ=,size_16,color_FFFFFF,t_70) ### 注意事项 任何在IO线程(也就是具有EventLoop)中绑定的Channel中发生的IO事件,最终都会在EventLoop中的handleEvent环节依次处理。 理解这一点非常重要,如果出问题需要debug,基本上都是在handleEvent环节中有线程安全的问题。 #### 其他辅助类 * `EventLoopThread` 封装了一个EventLoop的独立线程 * `EventLoopThreadPool` 封装了若干个EventLoopThread的线程池,所有者是一个外部的EventLoop * `Timer` 定时器相关的类 ## 2. 网络通信封装 ### TcpConnection 封装了一个TCP链接 * 主要接口 * send 发送数据的主要接口,最终通过内部实现在runInLoop中发送数据 * 回调setter * connectionEstablished 当连接建立时,应当只执行一次 将自身的shared_from_this指针与Channel绑定 令Channel激活对可读IO事件的关注 * connectionDestroyed 当连接断开时,应当只执行一次 将自己的Channel从所属EventLoop中移除 * 主要成员 * loop 主要回调都通过EventLoop所在线程处理 * Channel 通过Channel的回调调用自己的回调 * Socket 连接所属的套接字fd * localaddr,peeraddr 本地和对端socketaddr * 各种回调callback * inputbuffer,outputbuffer 应用层输入,输出缓冲区 ### TcpClient 客户端封装,需要注意的是,muduo的client类只负责一个连接 * #### Connector - TcpClient逻辑上的内部类 对于连接方法的封装 * 主要接口 * setNewConnectionCallback 设置TcpClient交给的回调函数 * start 最后通过loop的runInLoop调用 调用connect内部实现 * stop 最终通过loop的queueInLoop调用 回收Channel控制的套接字(如果有的话) 设置`connect_`标记为`false` * retry 若`connect_`标记为true,则重连 * 核心实现:connect 调用Socket::connect方法连接服务端 连接成功后,创建一个Channel 将自身的handleWrite回调注册到Channel上,并激活可写事件关注 * 核心实现:handleWrite 根据getSockError的情况决定调用创建连接回调,或是错误回调,或retry操作 其中包含了TcpClient注册的回调newConnection * 主要成员 * loop * channel unique_ptr指针,仅在连接建立时动态创建Channel对象 当channel触发可写事件时,执行handleWrite 并在handleWrite中执行TcpClient的newConnection * serverAddr * connector * connect_ 非常重要的一个标记,决定了是否retry * 主要接口 * 回调setters 这些回调函数会在新连接建立时,通过`newConnection`内部实现方法传递给`TcpConnction`对象 * 核心实现:newConnection 在构造时将这个函数作为回调注册给connector_对象 在Connector中的Channel执行本回调后,创建一个新的TcpConnection对象 * connect 调用Connector的start接口 * stop 调用Connector的stop接口 * 主要成员 * loop * `connector` TcpClient所维护的一个连接器 * `retry_` * `TcpConnection connection_` TcpClient所维护的一个TCP连接对象 关于连接中回调的传递,参考下面的简图: ![TcpClient回调传递的示意](https://img-blog.csdnimg.cn/eb602a687d5a49408a8209145c038271.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3didmFsaWQ=,size_16,color_FFFFFF,t_70) ### TcpServer 服务端封装 - muduo的server端维护了多个tcpconnection 注意TcpServer本身不带Channel,而是使用Acceptor的Channel * #### Acceptor - 逻辑上的内部类 接受器封装,实质上就是对Channel的多一层封装 * 主要接口 * listen 监听连接 当新连接进入时,调用Socket::accept创建套接字,触发TcpServer的回调 * setNewConnectionCallback TcpServer通过该接口设置回调,当新连接套接字创建后,创建TcpConnection对象 * 核心实现: 通过socket::accept接受新连接,获得套接字fd 这个fd作为参数调用TcpServer注册的回调 * 主要成员 * loop * channel * idlefd 非常巧妙的设计,在服务器压力过大,无法新建文件描述符时,通过这个idlefd拒绝连接 来自libevent的设计 * 主要接口 * 回调setters 这些回调函数会在新连接建立时传递给TcpConnction对象 * start 启动threadPool_线程池 在runInLoop中执行acceptor的listen 这里专门设置了一个started_标记,防止多次运行start * 核心实现:newConnection 从accept回调中获得的fd动态创建新的TcpConnection对象 为连接对象注册各类回调函数 将连接对象存入connectionMap_映射表里 * 主要成员 * loop 这个loop也是acceptor的loop * acceptor * threadPool 一个EventLoopThreadPool,用来存放io线程的EventLoopThread * socket 服务端用来监听的socket * ConnectionMap 一个连接名和实例(TcpConnectionPtr)的映射容器 TcpServer中回调的传递示意简图: ![TcpServer回调简图](https://img-blog.csdnimg.cn/da77bb9302ff4da9896f0154839d5b82.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3didmFsaWQ=,size_16,color_FFFFFF,t_70) ### Buffer 封装了一个可变长的buffer,支持廉价的前插操作,以及内部挪腾操作避免额外申请空间 ## 3. Socket API封装 ### Socket 封装了一个sockfd ### SocketOps 对socket设置API的封装 ### InetAddress 对sockaddr系列的封装 ### Endian 封装了字节序转换工具函数 ## 4. 基础类库封装 ### Logging 通用标准输出日志 ### AsyncLogging 异步文件日志 ### Date 日期类库封装