当前位置: 首页 > news >正文

细解muduo中的每个核心类

核心类:

channel:代表一个事件,打包起来,包含fd,event,以及回调函数

EpollPoller:继承抽象类Poller,事件监听器

EventLoop:IO多路复用,事件循环.一个EventLoop有一个EpollPoller:,负责监听多个channel,对于EventLoop中监听到的activeChannel进行回调(需要协调多线程)

在主线程(baseLoop)中,TcpServer通过Acceptor,有一个新用户连接,通过accept函数拿到connfd(通讯fd)  --->  打包TcpConnection设置回调,再把回调设置给channel,channel注册到poller上,poller监听到事件会就会调用channel的回调操作

1.Logger类

就是很简单的定义了日志类

类的成员有级别,

方法有获取唯一实例对象(因为是单例类),设置日志级别以及写日志

值得学习的是,在这里还定义了宏,四种分别根据Log级别定义(INFO,  // 普通信息 ERROR, // 错误信息 FATAL, // core信息 DEBUG, // 调试信息)。这样四个宏里面可以自动设置级别,和填入log信息(只需在用宏时填入要填入的信息,就像函数调用样,只不过说这个函数可以帮你自动做些事)

2.Timestamp类

在这个时间戳类中,

成员有

microSecondsSinceEpoch_(核心时间数据),意思是存储从 Unix 纪元(1970年1月1日 00:00:00 UTC) 开始到当前时刻经过的微秒数。

方法有

Timestamp()默认构造函数 ,初始化变量,创建表示1970年1月1日零点的时间戳

带参构造函数 explicit Timestamp(int64_t),从其他时间源转换

静态方法 static Timestamp now(),获取当前系统时间

格式化方法 std::string toString() const 将时间戳转换为易读字符串

3.Channel类

channel类是“事件通道”,把文件描述符(比如TCP连接fd)和IO事件(比如“可读”、“可写”)以及回调函数整合到一起

成员有

  1. *EventLoop loop_

    • 这个Channel属于哪个事件循环(线程)。
    • 事件循环对象,负责调用本Channel的回调。
  2. const int fd_

    • 代表关心的文件描述符,通常是 socket fd。
    • 比如一个客户端连接,fd_就是它的fd。
  3. int events_

    • 描述这个fd当前“感兴趣”的IO事件(比如读、写)。
    • 会被epoll等IO复用器使用,比如EPOLLINEPOLLOUT
  4. int revents_

    • poller返回的、fd刚刚发生的事件。
    • 由epoll等IO复用库返回,表示究竟发生了什么事件。
  5. int index_

    • 在Poller里的状态记录
    • 通常用于标记当前Channel在对应Poller数组里的状态,便于管理。
    • //感兴趣事件状态信息的描述

         index_ 状态含义:

      常量名含义
      -1kNew尚未添加到Poller
      1kAdded已添加到Poller
      2kDeleted已从Poller删除(但未移除)
  6. std::weak_ptr tie_

    • 用weak_ptr观测一个对象(通常是TcpConnection),这样Channel不会导致资源循环引用和内存泄漏。
    • 用于跨线程时保证被观测对象没被提前销毁。
    • //防止Channel被手动remove后,还在使用Channel,所以进行了一个跨线程的对象的生存状态的鉴定

          //shared_ptr和weak_ptr配合使用即可解决只使用shared_ptr的循环引用问题,

          //还可以用weak_ptr在多线程里监听它所观察的资源的生存状态

          //使用的时候可以把这个弱智能指针提升为强智能指针,提升失败说明观察的资源以及释放掉了

  7. bool tied_

    • 标记是否“绑过”对应对象(tie_)来做生存状态检测。
  8. 事件类型常量(static const int)

    • KNoneEvent :不关心任何事件。
    • KReadEvent :关心“可读”事件(数据可读)。
    • KWriteEvent :关心“可写”事件。
  9. 回调函数对象定义

    • using EventCallback = std::function<void()>;//事件的回调
    • using ReadEventCallback = std::function<void(Timestamp)>; //只读事件的回调
    • 这俩类型,分别用于“无参数事件回调”与“带时间戳的读事件回调”(方便统计业务延迟等)。
  10. 各事件回调对象

    • ReadEventCallback readCallback_
    • EventCallback writeCallback_
    • EventCallback closeCallback_
    • EventCallback errorCallback_
    • 由业务层配置(注册),事件发生时调用。

方法有

一、构造和析构

  1. *Channel(EventLoop loop, int fd)

    • 构造函数:新建一个Channel,指定它归属哪个事件循环、对应哪个fd。
    • 一般在 TcpConnection 初始化时创建。
  2. ~Channel()

    • 析构函数:销毁一个Channel对象

二、设置回调、关注/取消事件

  1. setReadCallback, setWriteCallback, setCloseCallback, setErrorCallback

    • 设置对应事件发生时要调用的函数。
    • 业务开发者写好回调后,通过这些设置进去。
  2. tie(const std::shared_ptr&)

    • 与一个对象(一般是TcpConnection)“绑定”,用weak_ptr监控它的生命周期,防止它还没处理完事件就被销毁。

三、事件注册、取消和状态控制

  1. enableReading, disableReading, enableWriting, disableWriting, disableAll

    • 关心/取消关注 某种事件(读/写等),并自动更新底层poller注册。
    • 例如,enableReading() 就是在感兴趣事件里标上“可读”,并通知epoll/poller关注。
    • 典型用途:需要关注某个fd上新的事件时调用。

一个EventLoop里有一个poller,要更新EventLoop里的channel得通过poller的方法,比如这里的update()函数,本质上是通过EventLoop调用poller的相应方法epoll_ctl

  1. update()

    • 通知底层Poller(epoll等),根据当前events_设置,重新注册fd事件。
    • 这是接口对下层epoll_ctl的透明封装。
  2. remove()

    • 把这个Channel从poller(IO复用器)里去除,不再监听。
    • 典型用途:连接关闭或对象即将销毁时。

四、事件处理核心

  1. handleEvent(Timestamp receiveTime)

    • 这个是“事件分发入口”。当fd有读/写/错误等事件发生,poller通知上来的时候调用,由它决定该调用哪种回调。
    • 如果tied_为true,先检测下被监控对象还在不在(guard),避免已经销毁;没问题再handleEventWithGuard
    • //实际上是用来保证channel对象是否还存活,若存活则调用handleEventWithGuard(Timestamp)
  2. handleEventWithGuard(Timestamp)

    • 真正干活的分发函数。根据revents_内容,依次判断

      • 有挂断 -> 调用closeCallback_
      • 有错误 -> 调用errorCallback_
      • 有数据可读 -> 调用readCallback_
      • 可写 -> 调用writeCallback_
    • 业务写的事件逻辑最终都在这些回调里实现,保证线程安全和资源不提前释放。


五、辅助函数(getter/setter)

  1. fd()

    • 返回Channel对应的fd。
  2. events()、set_revents()

    • 获取当前关心/设置刚发生的事件。
  3. index()、set_index()

    • 管理在poller里的索引/状态。
  4. ownerLoop()

    • 找到归属的EventLoop(事件循环)、便于跨对象回调。
  5. isNoneEvent()、isWriting()、isReading()

    • 查询当前感兴趣/被激活的事件类型。

 4.poller抽象类,抽象层

Poller 是“多路IO”复用器的抽象基类,屏蔽不同操作系统IO多路复用实现的差异

  • 常见实现有:selectpollepoll,在linux里一般用epoll
  • 它的职责:

    1. **登记:**记录所有想要监听的事件和fd。
    2. **监听:**高效检测这些fd上面是否有“就绪事件”发生。
    3. **分发:**把发生事件的fd(和对应的信息)通知上层(即Channel、EventLoop)。
  • 你可以把它理解为:“操作系统级的事件收发转运中心”,负责采集、上报所有网络或定时事件。

二、成员变量(属性)

1. EventLoop* ownerLoop_

  • 含义: 这个 Poller 属于哪个事件循环(EventLoop)?
  • 作用场景:
    • 保证线程安全
    • 未来可以通过这个指针找到归属它的EventLoop对象

2. using ChannelMap = std::unordered_map<int, Channel*>;

  • 含义: 映射表
    • key:文件描述符(fd或sockfd)
    • value:对应fd的Channel对象指针
  • 变量名: channels_
    • 维护所有被监控的通道(即所有被注册监听的fd及其对应的“管理者”Channel)
  • 生活类比:
    • 就像“班主任登记表”:每个学生学号(fd)都挂有自己的档案(Channel*)。
  • 作用:
    • 高效查找、插入、删除Channel
    • 检查某个fd是否已被监控

三、成员方法(接口)

1. 构造和析构

  • Poller(EventLoop loop)*
    • 构造函数,初始化Poller,标记归属的EventLoop
  • virtual ~Poller()=default;
    • 虚析构,确保多态删除安全,便于扩展(如epoll实现析构时回收资源)

2. 三大核心纯虚函数(必须由子类实现)

a) virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels)=0;
  • 作用:
    • “等事件发生”主接口。阻塞等待事件发生,返回活跃的 Channel 列表
    • 在timeoutMs毫秒内等待所有注册过的fd发生感兴趣的事件,发生了就填充到activeChannels列表,返回本次poll调用的时间(Timestamp)。
  • 生活类比:
    • 像是“班主任”定时巡视教室,看哪些学生(fd)举手(事件发生),把名单记录(activeChannels)交给上层处理。
  • 应用场景:
    • EventLoop::loop 主循环里会反复调用它,等待事件。
b) virtual void updateChannel(Channel* channel)=0;
  • 作用:
    • 把一个Channel对象对应fd感兴趣的事件注册到Poller里(比如添加或变更事件类型)。
    • 如果已经加过,可能只是更新事件类型(如从可读变成可写)。
  • 场景:
    • 新连接接入时注册;业务逻辑变更后需要更新(比如发完包可以暂停监听写事件)。
  • 生活类比:
    • 有学生(fd+Channel)需要登记新的兴趣爱好(事件类型),更新到名册(Poller/操作系统)。
c) virtual void removeChannel(Channel* channel)=0;
  • 作用:
    • 从Poller(操作系统的epoll/poll/等)注销一个fd和其所有事件,不再监听这个fd。
    • 通常在连接关闭、事件不再关心时调用。
  • 场景:
    • 连接关闭或对象销毁时调用。
  • 生活类比:
    • 学生毕业或者转学(fd注销),把他从名册里删了,不再关注。

3. 辅助接口

d) bool hasChannel(Channel* channel) const
  • 作用:
    • 检查Poller当前是否已登记(监听)了某个fd/Channel。
    • 通过查找channels_哈希表,看是否存在。
  • 场景:
    • 用于断言、调试、容错,避免重复操作。
  • 通俗类比:
    • “查查名册里有没有XXX这个同学的档案”。
  • 实现小建议:
    • 原代码此处命名有误,channel_ 应为 channels_
e) static Poller* newDefaultPoller(EventLoop* loop);
  • 作用:
    • 拓展点,工厂函数,创建特定平台的最佳Poller实现(如Linux一般返回epoll实现)。
    • 代码里可以只依赖基类,但底层实际用的是“最合适的派生类”。
    • 创建平台特定的 Poller 实例
    • 提供统一的接口创建不同实现的 Poller

    • 自动选择最佳 I/O 复用机制(Linux 用 epoll,macOS 用 kqueue)

    • 隐藏具体实现细节

  • 通俗说法:
    • “老板你说’我要一个能干活的Poller’,系统自动根据环境选给你最优的。”

5.EPollPoller类

一、类的整体定位

  • EPollPoller 是Poller的具体实现(派生类)。
  • 核心任务:利用epoll接口高效监听许多文件描述符(socket等)是否有事件(读、写、异常等)产生。
  • 适用场景:高性能网络服务器,支持大量连接。

二、成员变量(属性)

1. int epollfd_;

  • 含义: epoll实例的文件描述符。
  • 作用: 通过这个fd调用epoll_ctlepoll_wait进行事件管理;
  • 创建方式: 在构造时,调用::epoll_create1(EPOLL_CLOEXEC)创建。

2. EventList events_;

  • 定义: using EventList=std::vector<epoll_event>;,即定义一个vector<epoll_event>类型的成员。
  • 作用: 存储epoll_wait()返回的“就绪事件数组”。
  • 特点: 初始容量为kInitEventListSize=16,会根据需要动态扩大。

总结:

epollfd_ 管理epoll队列,events_ 数组存放等待通知的事件,是缓冲区。

想象 epollfd 就像一家快递公司的总控制台

  • 通过它可以管理所有快递柜(socket)

  • 可以监控哪些柜子有新快递(可读事件)

  • 可以监控哪些柜子有空位(可写事件)


三、常量定义(全局静态常量)

  • kInitEventListSize=16:初始监听数组大小(初始缓冲区大小)。

  • kNew=-1:表示Channel还未加入到Poller(未注册)。

  • kAdded=1:表示Channel已注册到epoll中(已监听)。

  • kDeleted=2:表示Channel曾被删除(不再监听)。

这些常量用来管理Channel的状态(对应index_成员)。


四、关键成员函数(方法)

1. 构造函数:EPollPoller(EventLoop *loop);

  • 作用: 初始化epoll实例和事件数组。
  • 重点:
    • epollfd_::epoll_create1()创建;
    • events_初始化容量;
    • 若创建失败,使用LOG_FATAL打印错误。

2. 析构函数:~EPollPoller()

  • 作用: 关闭epoll文件描述符,释放系统资源。

3. 核心工作:poll

Timestamp poll(int timeoutMs, ChannelList *activeChannels);
  • 产生作用: 等待事件发生。

  • 流程:

    • 调用::epoll_wait(),等待最多timeoutMs毫秒。
    • 将就绪的事件存入events_数组。
    • 根据事件数,调用fillActiveChannels()把事件关联的Channel指针装入activeChannels
    • 若事件数等于数组容量,动态扩容,提高效率。
    • 遇到错误:保存errno,打印日志。
  • 返回值:

    • 当前时间戳(Timestamp对象),标志事件的时间点。

4. 注册/修改/删除:updateChannel,用于更新channel状态,调用底层update

  • updateChannel 决定 "需要做什么"

  • update 负责 "具体怎么做"

  • 方法名作用调用场景主要职责
    updateChannel()高层操作:根据Channel状态,决定注册、变更或删除epoll监控EventLoop或其他代码调用,管理Channel的注册状态判断Channel当前状态,调用update()完成具体的epoll_ctl操作
    update()低层操作:封装epoll_ctl()系统调用updateChannel()内部调用,用于实际执行注册/修改/删除操作根据参数设置epoll_event结构体,调用epoll_ctl()执行操作
void updateChannel(Channel *channel);
  • 作用:

    • 根据Channel状态,调用epoll_ctl()将Channel对应fd注册、变更或删除监听。
  • 细节流程:

    • 判断Channel的权限状态(index_)
      • 新增(kNew)或已删除(kDeleted)
        • 调用epoll_ctl(ADD)注册;
        • channels_[fd]=channel存入映射;
        • 改变Channel状态为kAdded
      • 已注册(kAdded):
        • 如果没有感兴趣的事件(isNoneEvent()),调用epoll_ctl(DEL)注销;
        • 如果有事件,调用epoll_ctl(MOD)变更。
    • 调用底层update()完成epoll_ctl()实际操作。

5. 删除Channel:removeChannel

void removeChannel(Channel* channel);
  • 作用:
    • channels_哈希表中删除。
    • 若在监听(kAdded),调用epoll_ctl(DEL)取消。
    • 将Channel状态置为kNew

6. 填充活跃通道:fillActiveChannels

void fillActiveChannels(int numEvents, ChannelList* activeChannels) const;
  • 作用:

    • 遍历epoll_event数组。
    • 通过data.ptr找到对应的Channel,设置事件类型revents
    • Channel*加入到activeChannels,告诉EventLoop哪些通道有事件。
  • 注意:

    • epoll_event.data.ptr存的就是Channel* ,这是关键数据。

7. 底层epoll_ctl封装:update

void update(int operation, Channel *channel);
  • 作用:
    • 根据operationEPOLL_CTL_ADDEPOLL_CTL_MODEPOLL_CTL_DEL)调用::epoll_ctl()
    • 设置epoll_event的感兴趣事件和指向Channel的指针。
    • 失败时,输出日志。

五、总结

成员/方法作用关键点/细节
epollfd_epoll实例的文件描述符初始化调用epoll_create1()
events_存放就绪事件数组初始容量16,动态增长
构造函数创建epoll对象,初始化events_若创建失败,输出严重错误
poll()等待事件,获取就绪的Channel列表调用epoll_wait(),扩容、日志、时间戳返回
updateChannel()增删改Channel对应的fd在epoll中的状态依据index_值,决定注册、变更或删除
removeChannel()移除Channel,注销epoll上的监听从映射中删除,操作epoll_ctl(DEL)
fillActiveChannels()把发生事件的Channel放入活动队列枚举epoll_event数组,提取Channel*
update()调用epoll_ctl()封装底层epoll操作设置事件类型,处理错误

6.DefaultPoller类 

这里提供的代码片段其实是一个工厂函数(newDefaultPoller()),用于根据环境变量决定创建哪种Poller实例(使用poll还是epoll)。

你这个函数实现的逻辑是:

  • 如果环境变量"MUDUO_USE_POLL"被设置(存在环境变量),
    • 创建EPollPoller,可能会使用poll技术实现的Poller(暂未在这个代码里显示,但逻辑上应存在)
  • 否则,
    • 创建EPollPoller(基于epoll的高效事件轮询机制)

这实际上是多态工厂的一种应用:根据配置选择不同的事件通知机制(poll或epoll)。

  • Poller 很可能是一个抽象基类(接口类),规定了一组虚函数来管理“事件监听”,比如注册、修改、删除事件的操作。
  • EPollPoller是继承自Poller具体实现,专门利用epoll系统调用实现这些操作。
  • 其他可能存在的子类(不是在你的代码片段中)还可能有PollPoller(用poll()实现)等等。

7.EventLoop类

EventLoop类是一个网络多路复用的“核心调度器”,它负责管理事件监听、调度回调和协调多线程操作。

一、EventLoop的功能概述

  • 事件检测:通过Poller检测文件描述符(如:socket)是否有事件发生(读写、连接等)
  • 事件处理:调用对应Channel的回调函数处理事件
  • 线程安全调度:支持在不同线程中安全地加入任务(回调)
  • 跨线程通知:使用wakeup机制(如eventfd)唤醒事件循环,处理新加入的任务
  • 管理生命周期:启动、退出事件循环

二、成员变量详解(通俗版)

1. 控制变量

  • std::atomic_bool looping_:这个EventLoop是否在运行(循环在跑)
  • std::atomic_bool quit_:是否要退出循环(关闭事件循环)

2. 线程标识

  • const pid_t threadId_:标记这是在哪个线程创建的EventLoop,保证每个线程只能有一个EventLoop

3. 时间信息

  • Timestamp pollReturnTime_:上一次poll()调用返回的时间点,用来判断事件发生时间
  • (类里的其他时间相关变量)

4. 事件轮询机制

  • std::unique_ptr<Poller> poller_:封装poll/epoll等检测事件的对象(接口抽象)
  • ChannelList activeChannels_:刚检测到事件的通道(socket连接等)

5. 跨线程唤醒

  • int wakeupFd_:用来唤醒事件循环的文件描述符(通常是eventfd
  • std::unique_ptr<Channel> wakeupChannel_:监听wakeupFd_Channel

6. 任务队列

  • std::vector<Functor> pendingFunctors_:待执行的任务(回调函数)队列
  • std::mutex mutex_:保护pendingFunctors_的锁
  • std::atomic_bool callingPendingFunctors_:在执行回调时,避免并发导致重复调用

三、成员方法详解(通俗版)

生命周期管理

  • EventLoop():构造函数,初始化所有东西,并绑定当前线程
  • ~EventLoop():析构函数,清理资源(关闭fd、移除channel)

核心控制(运行/退出)

  • void loop():启动事件轮询和处理
  • void quit():请求退出事件循环

事件检测与处理

  • void handleRead()wakeupFd_读事件的处理函数(被唤醒时调用)
  • Timestamp pollReturnTime() const:获取上次检测到事件的时间

任务调度(跨线程执行回调)

  • void runInLoop(Functor cb):在当前线程立即执行回调(如果在自己线程)
  • void queueInLoop(Functor cb):把回调放到队列,异步在自己线程执行
  • void doPendingFunctors():执行队列中的任务(在循环中调用)
  • bool isInLoopThread() const:判断当前是否在自己创建的EventLoop所在的线程

通信机制

  • void wakeup():用eventfd通知EventLoop有新任务(唤醒事件循环)

Channel管理

  • void updateChannel(Channel *channel):注册或修改某个通道的事件
  • void removeChannel(Channel *channel):取消某个通道
  • bool hasChannel(Channel *channel):检测是否管理某个通道

四、工作流程简要(通俗版)

  1. 创建EventLoop实例时,初始化通知唤醒机制、Poller,确保每个线程只有一个EventLoop
  2. 调用loop(),开始无限循环等待事件:
    • 使用poller_检测文件描述符是否有事件
    • 如果有,调用对应的Channel处理
    • 还会处理pendingFunctors_队列中的任务(来自其他线程)
  3. 跨线程调度任务
    • 其他线程调用queueInLoop(),加入任务队列
    • 通过wakeup()唤醒自己,及时执行新加入的任务
  4. 退出循环
    • 调用quit(),设置退出标志,下一轮检测到退出,安全退出。

五、总结(简洁版)

成员类型作用
looping_控制是否在运行的标志
quit_控制是否退出的标志
threadId_创建时的线程ID,保证每个线程只有一个EventLoop
poller_事件检测(poll/epoll)封装对象
activeChannels_发生事件后,存放激活中的Channel列表
wakeupFd_用于跨线程通知EventLoop有新任务
wakeupChannel_监听wakeupFd_,当有通知时会触发回调
pendingFunctors_存放待执行任务的队列
mutex_保护pendingFunctors_的线程安全,确保多线程安全操作

8.thread类

Thread 类是 muduo 网络库中线程管理的核心组件,它封装了线程的生命周期管理、线程标识和线程函数执行。

Thread类是对C++标准库线程std::thread的封装,让多线程编程更易用、易管理、更安全。

  • 它可以方便地在C++程序中启动一个新的线程来运行一个函数。
  • 并且可以给线程命名,拿到线程号,管理线程的生命周期(启动/等待结束)。
  • 防止错误复制(通过继承noncopyable禁止拷贝)。

二、成员变量详解

成员变量含义与作用通俗解释
ThreadFunc func_线程要执行的函数你在线程中想执行的代码(比如:服务器主循环、任务逻辑等)
std::shared_ptr<std::thread> thread_线程对象指针底层真实的一条C++线程
bool started_已启动标记线程是否启用过(是否start过)
bool joined_已回收标记线程是否已经join(回收)过
pid_t tid_线程ID子线程自己的ID号(系统分配的),便于调试
std::string name_线程名字给线程起的名字(方便排查、多线程调试日志时看得懂)
static std::atomic_int numCreated_已创建的线程数全局的线程计数器,每创建一个累加一次

三、核心成员方法详细解释

1. 构造与析构

  • Thread(ThreadFunc func, const std::string &name)

    • 构造函数,传入线程要执行的函数,以及线程名称(可选)。
    • 只做“准备”,不会马上启动线程。
    • 会给name_设一个默认名字(比如Thread1Thread2等),如果没给的话。
  • ~Thread()

    • 析构函数,如果线程已start但没join,会把此线程设成“分离”线程(daemon/守护线程,主线程结束它也跟着结束),防止资源泄漏。

2. 线程控制

  • void start()

    • 真正启动线程(只会启动一次)。
    • 内部用lambda表达式,将线程主函数包装进一个std::thread并启动。
    • 新线程会保存自己的线程id(tid_),并通过sem_t信号量同步回主线程,确保主线程可以拿到此id。
    • 线程启动后会立即执行你传进来的func_任务。
  • void join()

    • 等待线程运行结束,相当于“回收线程”。
    • 防止主线程太快退出导致子线程没做完。

3. 线程信息&工具方法

  • bool started() const

    • 当前线程是否已启动过。
  • pid_t tid() const

    • 返回线程id。
  • const std::string& name() const

    • 返回线程名字。
  • static std::atomic_int32 numCreated_()

    • 返回创建过的线程总数。
  • void setDefaultName()

    • 若name_为空的话,自动生成一个如“Thread1”名字。

四、Thread类的使用举例(通俗表述)

1. 如何用

void myTask() {// do something
}Thread t(myTask, "worker1");
t.start();  // 开启新线程,执行myTask
t.join();   // 等待myTask执行完毕

2. 你可以得到什么?

  • 每个Thread能执行你给他的函数
  • 每个Thread会被命名,log和调试很友好
  • Thread对象管理资源,用起来很安全,不会泄漏

9.EventLoopThread

一、类的设计理念

EventLoopThread 的作用:

  • 用于在单独的线程中创建和管理一个独立的 EventLoop(事件循环)对象。
  • 典型用途是网络编程中“一个IO线程管理一个事件循环”(one loop per thread)的模式,解耦多线程下的事件驱动机制,让每个线程专心干自己的事。

打个通俗的比方:

EventLoopThread 就好像是“线程+事件循环”的捆绑套餐,一旦启动就创建一个线程,线程内专心跑一个事件循环。

二、成员变量剖析

成员类型作用/通俗解释
EventLoop* loop_指针,指向线程内的EventLoop对象指向自己线程里那个“主循环”的地址;主线程可通过它和该子线程的事件循环通信
bool exiting_布尔值标记是否要退出(析构清理用)
Thread thread_Thread类对象真正的工作线程(管理线程的启动与停止)
std::mutex mutex_互斥锁用于多线程安全;主线程和子线程间同步数据用
std::condition_variable cond_条件变量配合mutex,让主线程等子线程做好准备(如eventloop创建好)
ThreadInitCallback callback_回调函数类型可选:线程初始化时可执行自定义操作(如设置优先级、绑定资源等),函数签名是void(EventLoop*)

三、构造与析构

1. 构造函数

EventLoopThread(const ThreadInitCallback &cb = ThreadInitCallback(), const std::string &name = std::string());
  • 可以传一个初始化回调(cb),和线程名(name)。
  • 内部会把线程主函数绑定为自己类的threadFunc这个成员函数。
  • 此时只是准备“套餐”,线程和EventLoop都还没真正启动。

2. 析构函数

~EventLoopThread();
  • 如果事件循环存在,调用它的quit()让loop干净退出。
  • 并确保线程通过join被正确“回收”,防资源泄漏。

四、核心方法详解

1. EventLoop* startLoop()

作用:

  • 启动专属线程,并在其中创建并运行一个新的EventLoop。
  • 整个过程主线程会一直等待,直到子线程的loop对象创建好并赋给loop_,保证线程和loop都准备好了之后才返回。

流程简述:

  1. thread_.start() 启动线程(threadFunc开始运行)。
  2. 主线程用 mutex+cond_ 一直等,直到loop_被子线程线程内正确赋值。
  3. 返回loop指针(主线程可通过它和事件循环通信)。

通俗解释:

就像启动一个外包小队(新线程),你(主线程)必须等小队长(loop对象)向你报平安(cond_),你才用它去接任务。


2. void threadFunc()

作用:

  • 真正线程里要做的事,全在这里!

主要流程:

  1. 在线程内栈上创建一个独立的EventLoop对象。
  2. 如果有初始化回调(callback_),就拿EventLoop*给它调用一下,便于用户做些特殊初始化。
  3. loop_赋值为新建的loop地址,唤醒主线程(通知cond_条件变量)。
  4. 进入事件循环:loop.loop(),开始处理IO和事件。
  5. loop退出后,清空loop_指针(生命周期彻底结束)。

通俗解释:

threadFunc就像让新员工(新线程)“入职”:布置好办公桌(创建EventLoop)、调用上岗前培训(callback_)、汇报工号(loop_赋值+通知),然后开始正式上班(loop.loop),下班再交接走人。


五、整体使用场景说明

举例,如果你想给每个网络连接分配一个专用线程和事件循环,可以创建多组 EventLoopThread(比如 N 个 IO线程池),让每个 EventLoopThread 独立管理自己的 loop,轻松做到多线程高并发处理。

EventLoopThread *worker = new EventLoopThread();
EventLoop *loop = worker->startLoop();
// 现在 loop 已经在专门线程里跑起来了,你可以通过 loop 在线程间传递任务了。

六、总结优势

  • 隔离线程和事件循环:每个EventLoop专属一个线程,不串台,线程安全。
  • 流程清晰:主线程等子线程准备完毕再进行后续操作,满足并发语境下严格的同步要求。
  • 扩展友好:支持初始化回调,易于做定制扩展。
  • 用起来简单:整体接口高度抽象,开发者仅需关心入口输出,线程和事件循环的细节自动管理。

10.EventLoopThreadPool

一、类的设计定位

EventLoopThreadPool 类的主要作用是:

  • 用于网络编程中,自动化管理多个事件循环(EventLoop)和线程,提升服务器并发能力。
  • 让你“一下子”轻松拥有多个 I/O 线程,每个线程管理一个 loop,能轮询分配新连接或任务。

可以直观比喻它:像前台小经理,统筹安排多个工位(线程)和员工(loop)轮流上岗。


二、成员变量详解

变量名类型作用通俗解释
baseLoop_EventLoop*主 loop,通常属于主线程,用于没有子线程时的工作“自己亲自干活”时用的 loop
name_std::string线程池名字,调试或日志有用给整个线程池起个好听的名
started_bool标记线程池是否已经启动线程池开张了吗?
numThreads_int池内有几个 EventLoopThread我手下有几位员工?
next_int记录下次该把任务给哪个 loop排班轮到谁了?
threads_std::vector<std::unique_ptr<EventLoopThread>>保存每一个线程与其管理对象手下所有 IO 员工 threading 的指针集合
loops_std::vector<EventLoop*>保存每一个线程里的 EventLoop 指针每个员工的工位(事件循环)集合

三、构造与析构

构造函数

EventLoopThreadPool(EventLoop *baseLoop, const std::string &nameArg)
  • 由外部传入主线程的 EventLoop 对象指针和线程池名字。
  • 初始并不创建线程或 loop,仅记录参数。

析构函数

~EventLoopThreadPool()
  • 线程池析构时,智能指针unique_ptr会自动释放每个 EventLoopThread。线程和loop都能被安全自动清理。

四、主要方法逐个解释

1. void setThreadNum(int numThreads)

设置线程数(工位数)。

  • start之前调用。
  • 例如传 4 就表示准备起 4 个专属线程(和 loop)。

2. void start(const ThreadInitCallback &cb = ThreadInitCallback())

正式启动线程池,创建所有线程和它们的 EventLoop。

底层流程:

  1. 启动标记started_ = true,以后不再重复启动。
  2. 循环 n 次:为每名工人(线程)分配:
    • 一个 EventLoopThread 对象
    • (用 unique_ptr 管理,自动释放)
    • 调用它的 startLoop() 方法,实际启动线程+创建 loop,并保存 loop 指针。
  3. 特殊情况:如果线程数是0,只用主线程(baseLoop_)处理所有事件。这时如果传了回调,主线程的 loop 也做必要初始化。

通俗解释:

老板说“大家开始上班!”,于是经理把所有工位打开,每个员工(线程+loop)都 ready 了。如果人力不足(线程数0),老板亲自上!


3. EventLoop* getNextLoop()

作用:轮询分配下一个 loop。

底层流程

  • 若没有线程池(loops为空),一直返回主 loop(单线程)。
  • 有子线程时,采用“轮流值班”,把事件分配给队列中的下一个 loop。

通俗解释:

前台经理安排客户,每来一个就交给下一个工位,公平轮换(负载均衡)。


4. std::vector<EventLoop*> getAllLoops()

返回所有(活着的)loop指针。

  • 如果没有子线程,则只返回主线程的 base loop;
  • 有子线程,则返回所有子线程的 loop 指针数组。

通俗解释:

想批量通知所有员工或广播任务,就找经理(getAllLoops)问一声,他能告诉你每个人的位置。


5. 其他接口

  • bool started() const :线程池是否已运行。
  • const std::string name() const:返回线程池名字。

五、典型使用场景与好处

  1. 适合高并发 server:每个连接或任务,可以均衡轮询分发到各个独立 thread+loop(不会“挤死”某一线程)。
  2. 方便多线程通信:有统一的接口分配和回收 worker loop。
  3. 支持回调自定义初始化:后期扩展时可以灵活调整 loop/thread 初始化细节。

六、例子通俗比喻梳理

假设你要开一个烧烤摊,平时只有你一个人(baseLoop_),忙不过来时就招了几个小工(EventLoopThread)。顾客(channel/任务)来了,你轮流安排各个小工接待,大家互不影响,非常高效!


七、可能的扩展说明

  • 线程数支持动态设置(setThreadNum)。
  • 可以提供更多定制的线程初始化操作(start的回调)。
  • 这个池子适合做 TCP server、网络代理等高并发服务的 event-loop 后台。

八、总结

EventLoopThreadPool 是线程+事件循环“打包发”的智能调度中心。

  • 不用手动管理每个线程和loop。
  • 能自动做并发负载均衡,节约开发精力。
  • 提供取下一个“工位”、取全部工位的工具接口。

11.CurrentThread类

一、整体概览

这个CurrentThread模块的主要目的是:

  • 快速获取当前线程的ID(tid)
  • 避免每次都调用系统级的syscall,提升效率(通过缓存)

简而言之,就是帮你“偷懒”,每次用到线程ID时,一次性“记下来”,后续再用就不用调用啥系统级别的繁琐操作。


二、成员和方法详细分析

1. __thread int t_cachedTid

  • 类型__thread int
  • 作用:存储当前线程的ID(tid),这是一个线程本地存储(thread-local storage),每个线程都拥有自己的副本。
  • 通俗比喻:想象每个工人在不同工厂(线程里)都拥有一个“记事本”,写上自己ID,避免别的工厂记错。

2. void cacheTid()

  • 作用:给当前线程的 t_cachedTid 赋值,记录这个线程的真实ID。
  • 实现方式:调用 Linux 系统调用 syscall(SYS_gettid) 获取当前线程的唯一ID。

通俗理解

  • 这就像给每个工人(线程)“拍照”记录他们的身份证(ID),以后不用再照相。

3. inline int tid()

  • 作用:快速返回当前线程的ID (tid)。
  • 实现流程
    • 首先检查 t_cachedTid 是否为0(还没获取过)。
    • 如果为0,则调用 cacheTid(),执行“拍照”动作,存下来。
    • 最后返回 t_cachedTid

为什么要这样设计?

  • 调用系统syscall获取ID比较慢(因为陷入内核态)。
  • 通过在第一次调用的时候缓存(拍照)下来,以后都直接用缓存的值,提高效率。

特殊点

  • __builtin_expect() 是一个 GCC 内置函数,用于优化预测(告诉编译器:t_cachedTid=0的概率很低),以优化分支预测,提升运行速度。

通俗理解

  • 就像第一次你去银行取ID(花时间),之后每次用ID只需要看记事本(缓存)就行了。

三、代码结构

你的代码实际上分成两个文件,但都属于命名空间CurrentThread,内容如下:

1. 头文件 (声明部分,带namespace

复制代码

namespace CurrentThread
{extern __thread int t_cachedTid; // 声明void cacheTid(); // 声明inline int tid(); // 声明
}

2. 源文件(定义部分)

// 定义静态线程局部变量
__thread int CurrentThread::t_cachedTid = 0;// 定义缓存函数
void CurrentThread::cacheTid()
{if(t_cachedTid == 0){t_cachedTid = static_cast<pid_t>(::syscall(SYS_gettid));}
}

注意:extern声明是为了让其他文件引用这个__thread变量。


四、详细总结:它的“成员”和“方法”都在干啥?

组件类型作用通俗比喻
__thread int t_cachedTid线程局部变量用于存放当前线程的ID,确保每个线程自己的“身份证”独立存放每个工厂有自己的“身份证本子”
void cacheTid()函数用系统调用获取当前线程的ID,存到t_cachedTid让工人“拍照”并存下身份证号码
inline int tid()内联函数快速返回当前线程ID,如果没拍照就调用cacheTid()拍照存下来工人用“身份证本子”查身份证号码,没有就“让工人拍照”

五、它的作用总结

  • 效率:第一次调用tid()时会很慢(因为调用系统调用),但之后就直接用缓存,速度快很多。
  • 简单方便:你不用每次都去操作复杂的系统调用,只需调用CurrentThread::tid()即可直接拿到线程ID。
  • 适用场景:多线程程序中,需要知道当前在哪个线程执行(比如日志输出带线程ID,或调试追踪)。

六、建议与扩展

  • 这个设计很经典,常用于网络框架、调试信息、日志系统中。
  • 你也可以扩展,加入thread_name()等信息,丰富调试信息。

12.Socket类

这个Socket类是个封装网络编程中socket文件描述符(fd)的工具类,旨在简化和管理TCP连接相关的操作。让我们逐一介绍它的成员变量和成员函数,力求清楚、详细、通俗易懂。


一、成员变量

private:const int sockfd_;
  • sockfd_:这是Socket类中的核心成员,是封装的“套接字文件描述符”。
    • 作用:标识一个具体的网络连接或监听端口的数字编号(由系统分配,类似“房间编号”)。
    • const修饰:表示一旦创建,不能更改,确保每个Socket对象对应一个唯一的fd。

二、构造函数

explicit Socket(int sockfd): sockfd_(sockfd)
{}
  • 作用:创建一个Socket实例,绑定给定的socket fd
  • 参数
    • sockfd:已创建的socket(比如socket()调用返回的fd或accept()返回的连接fd)。
  • explicit:避免隐式类型转换,保证传入参数明确。

三、析构函数

~Socket();
  • 作用:当Socket对象销毁时,自动关闭对应的socket资源。
void Socket::~Socket()
{close(sockfd_);
}
  • 说明:调用系统APIclose()关闭fd,释放资源。

四、核心成员方法(操作socket)

1. bindAddress

void bindAddress(const InetAddress &localaddr);
  • 作用:将这个socket绑定到某个特定的IP地址和端口(设为监听端口)。
  • 具体操作:调用系统bind()
  • 参数
    • localaddr:封装的网络地址(IP、端口)。

2. listen

void listen();
  • 作用:将socket设置为监听状态,等待远端连接请求。
  • 调用系统APIlisten(),参数1024表示允许的连接队列最大长度。
  • 注意:在调用listen()之前要确保socket已经绑定。

3. accept

int accept(InetAddress *peeraddr);
  • 作用:接受客户端的连接请求,返回连接的socket fd(新连接文件描述符)。
  • 两个关键点
    • 它会阻塞直到有连接到来(除非设置为非阻塞)。
    • 如果成功,会把客户端的地址信息(IP、端口)存入peeraddr
  • 参数
    • peeraddr:传入一个指针,调用成功会填充远端客户的地址信息。
  • 特殊点
    • 使用accept4(),结合标志SOCK_NONBLOCK | SOCK_CLOEXEC
      • SOCK_NONBLOCK:让新连接的fd是非阻塞的,避免阻塞调用。
      • SOCK_CLOEXEC:确保在后续exec()调用时自动关闭这个fd。
  • 返回值:新连接的socket fd,如果出错返回-1

4. shutdownWrite

void shutdownWrite();
  • 作用:优雅地关闭连接的写端。
  • 调用系统APIshutdown(sockfd_, SHUT_WR)
  • 作用:告诉对端“我不再发数据了,你可以读完”。
  • 出错处理:如果失败,用LOG_ERROR记录。

5. 设置socket的各种属性(用于调优和优化)

  • setTcpNoDelay(bool on)

    禁用Nagle算法(关掉TCP_DELAY),减少延迟,适合实时通信。

  • setReuseAddr(bool on)

    允许快速重用地址,避免端口占用问题。

  • setReusePort(bool on)

    允许多个socket绑定到同一个端口(多核负载均衡场景常用)。

  • setKeepAlive(bool on)

    设置保持连接的检测机制,避免死连接。


五、总结:这个Socket类的作用和用途

  • 封装底层操作:把底层的socket()bind()listen()accept()shutdown()setsockopt()封装成易用的成员函数。
  • 使用简易:调用者只需创建Socket对象,然后调用相关成员函数,不必重复写繁琐的系统调用,减少出错。
  • 资源管理:在对象销毁时自动关闭fd,保证资源不泄露。
  • 配置参数:提供方便的方法,支持设置TCP参数(如禁用Nagle算法、开启地址重用等)。

贴心总结

成员/方法作用简单理解
sockfd_管理的socket编号(文件描述符)“我的房间号”
构造函数初始化Socket对象,将已有socket fd绑定到对象上“进入房间”
~Socket()析构函数,关闭socket资源“离开房间”
bindAddress()给socket绑定IP和端口“在房子门口挂牌”
listen()设置socket为监听模式“开门迎客”
accept()接受一个客户端连接,返回新连接的fd和客户端地址“迎接客人”
shutdownWrite()关闭连接的写端,结束发送数据“说完话,不发了”
其他set...()设置一些网络参数,优化连接性能或行为“调教网络”

13. Acceptor 

一、总体作用 

Acceptor 的职责就是监听(listen)端口,一旦有新客户端“敲门”(即有新连接请求),它负责接受这个连接,并把这个新连接交给后续的处理(比如,分配给工作线程)。

它解耦了“侦听新连接”和“业务处理/读写”的职责,让整个TCP服务器结构更清晰,也支持高性能的事件驱动(Reactor)模型。


二、成员变量详细解释

1. loop_

EventLoop *loop_;
  • 指向“主事件循环”(mainLoop),主线程用来管理监听socket和接收新连接事件。
  • 每个 Acceptor 只在一个 EventLoop(主循环)中活跃。

2. acceptSocket_

Socket acceptSocket_;
  • 用来监听的 socket(服务端的 listenfd)。
  • 专门用于接受新客户端连接。

3. acceptChannel_

Channel acceptChannel_;
  • 事件通道对象,把 acceptSocket_ 的“读事件”注册到 EventLoop。
  • 当有新连接到来,这个 Channel 会捕捉到事件,从而调用回调函数。

4. newConnectionCallback_

NewConnectionCallback newConnectionCallback_;
  • 新连接到来时要执行的回调(functor):void(int sockfd, const InetAddress&)
  • 通常,这个回调函数会把新连接(包括fd和客户端地址)交给 TcpServer/线程池 的具体某个“子Loop”继续处理。

5. listenning_

bool listenning_;
  • 表示当前是否处于监听状态(即 server 是否已主动“开门迎客”)。

三、成员方法详细解释

1. 构造函数

Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport);
  • 主要做了以下几件事:
    1. 创建一个非阻塞的 listen socket。
    2. 设置 socket 选项(地址复用、端口复用)。
    3. 绑定地址 listenAddr。
    4. 创建 Channel,绑定到主 EventLoop。
    5. 给 Channel 设置“读事件回调”——即有新连接时调用 handleRead。
  • 通俗理解:像物业经理一样,拿到自己的大门(socket),贴好门牌号(bind),告知保安(Channel)哪怕半夜有人敲门都通知自己。

2. 析构函数

~Acceptor();
  • 资源清理,关闭 Channel 对事件的监听并从 EventLoop 解除注册。

3. setNewConnectionCallback

void setNewConnectionCallback(const NewConnectionCallback &cb);
  • 设置新连接到来时的响应函数(回调)。
  • 外部(比如 TcpServer)可将自己的处理逻辑交给 Acceptor。

4. listenning

bool listenning() const;
  • 返回当前 Acceptor 是否处于“监听”状态,可用于状态判断。

5. listen

void listen();
  • 真正让 socket 进入监听状态,告诉内核开始处理客户端连接。
  • 同时让 Channel 检查“读事件”,即有人连上就立刻捕捉到并回调。

6. handleRead(核心!)

void handleRead();
  • 职责
    1. 有新连接到来时,被 EventLoop/Channel 调用。
    2. 调 accept,获得新连接的 fd 及对方地址。
    3. 如果设置了 newConnectionCallback_,就把新连接交给注册方处理。
    4. 若回调未设置,则立即关闭这个新fd,避免泄露。
  • Resilience
    • 错误处理(比如 EMFILE:文件描述符已满)。
    • LOG 错误信息。

7. createNonblocking(静态函数)

static int createNonblocking();
  • 工厂方法,辅助生成一个“非阻塞+close on exec”的 TCP socket fd。

四、通俗理解——为什么要这样设计?

  • 分工明确: Acceptor 只负责“开门、验票”,新客人的后续处理(比如对话、聊天)由其他模块负责。
  • 事件驱动: Channel 负责监听事件,EventLoop 负责事件循环,Acceptor 只实现回调。
  • 高性能:使用非阻塞fd,配合Reactor模式,可以单线程高效管理海量连接。
  • 能扩展:后续可以配合线程池,把新连接均衡分配给多个Worker线程处理。

总结表格

成员/方法作用/用途通俗解释
loop_指哪一个主事件循环用来监听“门铃”谁来守大门
acceptSocket_封装的服务端监听 socket(listenfd)大门(监听入口)
acceptChannel_事件通道,捕捉新连接事件并分发“门铃”与安保
newConnectionCallback_有新连接就调用此回调“有客人了,你来接待吧”
listenning_当前是否“开门迎客”显示“营业中”
Acceptor(), ~Acceptor()构造/析构函数,初始化/资源回收房子装修/拆除
setNewConnectionCallback()设置“新客人”到来时的接待处理方式用户自定义“怎么接待客人”
listen()进入正式“营业”状态,开始监听掀开帘子,开门迎客
handleRead()有新连接自动处理(收下新fd/分发回调/关门拒接等)门铃一响立刻开门

总结一句话:

Acceptor 就像TCP服务器中的前台/门卫,它只负责等人敲门,一旦有新客户,它就招呼对方进门,并把客人交给内部工作人员继续服务!

14.TcpServer类

这个 TcpServer 类是一个高层次封装的网络服务器类,负责整体管理新连接、连接维护以及调度工作。下面我会用通俗易懂的方式,详细介绍它的所有成员变量和方法。


一、TcpServer 类的主要作用

  • 创建并管理监听端口(主端口)
  • 支持多线程(线程池)处理多个客户端
  • 管理所有活跃连接(TcpConnection)
  • 在连接建立、消息到达、连接关闭等场景下提供回调函数接口
  • 让用户简单快捷地写出高性能的TCP服务器程序

二、成员变量详细介绍

成员变量作用/描述通俗解释
EventLoop *loop_指向主事件循环(主线程的事件循环)服务器核心循环,用来管理所有事件
const std::string ipPort_绑定监听的IP和端口字符串比如 “192.168.1.100:8888”,知道服务器在哪个地址监听
const std::string name_服务器的名字方便识别和日志记录
std::unique_ptr<Acceptor> acceptor_监听新连接的“守门员”,实际做监听工作让它负责处理“谁来报到”
std::shared_ptr<EventLoopThreadPool> threadPool_线程池,管理多个子事件循环(多线程处理)支持多核CPU,提升性能,让多连接同时处理无压力
ConnectionCallback connectionCallback_新连接建立时的回调函数用户可以定义“客户端连接来了,要干啥”
MessageCallback messageCallback_收到消息后的回调用户定义“收到消息后,怎么处理”
WriteCompleteCallback writeCompleteCallback_消息发送完毕的回调用于处理消息被成功发出的逻辑(比如确认通知)
ThreadInitCallback threadInitCallback_线程初始化(启动子Loop线程时调用)用户可以设置每个线程的初始化逻辑
std::atomic<int> started_记录服务器是否已启动(防止重复启动)多线程安全,控制start只调用一次
int nextConnId_用于生成唯一连接名的编号每个连接一个编号,确保名字不重复
ConnectionMap connections_存放所有活跃的连接对象(映射:连接名 -> TcpConnection)管理当前所有在线上的客户端连接

三、关键方法逐一讲解

1. 构造函数 TcpServer()

TcpServer(EventLoop *loop, const InetAddress &listenAddr, const std::string &nameArg, Option option);
  • 作用:初始化服务器对象
  • 做的事情:
    • 检查 loop 不为空(用 CheckLoopNotNull()
    • 创建监听 socket(Acceptor)
    • 设置监听的地址和端口
    • 创建线程池(用 EventLoopThreadPool
    • 设置 Acceptor 的新连接回调为类中的 newConnection()
    • 通俗:搭建好“门”和“工作团队(线程池)”。

2. 析构函数 ~TcpServer()

~TcpServer();
  • 作用:清理资源
  • 会逐个关闭所有的连接(调用 connectDestroyed()
  • 通俗:像收摊,逐个打扫“流水线”上的摊位,确保不留下脏东西。

3. 设置各类回调接口

void setConnectionCallback(const ConnectionCallback &cb);
void setMessageCallback(const MessageCallback &cb);
void setWriteCompleteCallback(const WriteCompleteCallback &cb);
void setThreadInitCallback(const ThreadInitCallback &cb);
  • 用户可以通过这些接口定义自己处理“新连接”、“消息到达”、“消息发送完毕”和“线程初始化”的逻辑。

4. setThreadNum(int numThreads)

void setThreadNum(int numThreads);
  • 设置工作线程池中的线程数量
  • 只有调用后,线程池才会启动,支持多线程处理

5. start()

void start();
  • 启动服务器模型
  • 作用包括:
    • 防止重复调用(用 started_
    • 启动线程池(调用 threadPool_->start()
    • 开始监听(调用 acceptor_->listen()

6. newConnection()

void newConnection(int sockfd, const InetAddress &peerAddr);
  • 核心功能:当acceptor检测到有新客户端连接时调用
  • 作业:
    • 选择一个子Loop(用线程池)
    • 创建唯一名字,name_ + "-ip-port-#id"
    • 创建 TcpConnection 对象(代表这条连接)
    • 保存到 connections_(方便管理和关闭)
    • 设置该连接的各种回调(处理消息、连接建立、关闭)
    • 让子Loop调用 connectEstablished(),正式开始处理
  • 通俗:新客户来了,找一个“服务员(线程)”来陪他聊天,记下他的“档案”。

7. removeConnection() 和 removeConnectionInLoop()

void removeConnection(const TcpConnectionPtr &conn);
void removeConnectionInLoop(const TcpConnectionPtr &conn);
  • 作用:关闭连接
  • 通常由 TcpConnection 在检测到连接关闭后调用
  • 处理:
    • 从 connections_ 删除
    • 调用 connectDestroyed(),清理资源
  • 通俗:跟客人说再见,清理“档案袋”,让连接端正退出。

四、核心流程总结(用户场景)

  1. 用户创建 TcpServer
  2. 配置回调(连接、消息等)
  3. 设置线程池大小
  4. 调用 start() 开始服务
  5. 服务器开始监听端口
  6. 一旦有客户端连接,acceptor 触发 newConnection()
  7. newConnection() 找个子Loop创建 TcpConnection,并激活它
  8. 用户自定义的回调(比如处理数据)被调用
  9. 客户端关闭时,TcpConnection检测到,通知 TcpServer 清理连接

五、总结:它像什么?

TcpServer 就像一个大型的“接待大厅”,

  • acceptor是“门卫”负责迎客;
  • 线程池是“多个服务员”同时服务多客;
  • **connections_**是“在场的客人”名单;
  • 回调是“提前安排好的接待流程”。

14.TcpConnection类

这个TcpConnection类是一个关键的网络连接类,它封装了一条实际的TCP连接(socket),管理这条连接的所有细节,比如数据的收发、连接的建立和关闭,以及各种事件的回调。下面我会以通俗易懂的方式,详细介绍它的成员变量和方法。


一、TcpConnection类的作用

  • 表示一次TCP连接
  • 管理连接的状态(是否连接上)
  • 负责数据的读取和写入
  • 提供接口设置用户的回调(连接事件、消息事件、写完事件等)
  • 正确处理连接的建立和关闭流程

二、成员变量详细介绍

成员变量作用/描述通俗解释
EventLoop *loop_ 所属的事件循环(子loop),不是主loop(用来处理这个连接的事件)负责管理这个连接的事件(读写等),不是全局的主环
const std::string name_连接的名字(标识符)方便日志和管理
int state_当前连接状态(连接、等待、断开等)用枚举值表示:已连接、连接中、断开中等。
bool reading_是否正在读取数据用于控制读取状态(比如暂停读取等)
std::unique_ptr<Socket> socket_socket封装对象,操作底层socket实现对socket的封装(如设置socket选项,关闭等)
std::unique_ptr<Channel> channel_事件通道,管理事件(读写等)交给poller检测连接的各种事件(可读、可写、异常)都由Channel负责监控
const InetAddress localAddr_本地IP地址信息连接端的本机地址
const InetAddress peerAddr_对端(客户端)地址信息客户端的地址信息
ConnectionCallback connectionCallback_连接状态变化的回调(连接建立或断开)用户定义的,连接成功或断开后要做的事
MessageCallback messageCallback_收到消息时的回调用户定义,处理收到的数据
WriteCompleteCallback writeCompleteCallback_发送完整后的回调用户定义,数据全部发出后处理
HighWaterMarkCallback highWaterMarkCallback_水位回调(缓冲区达到阈值触发)发送缓冲区太大时通知用户
CloseCallback closeCallback_连接关闭时的回调用户定义的关闭处理,比如从Server中移除连接
size_t highWaterMark_缓冲区水位线(阈值)大小设为64MB,超过会触发高水位回调
Buffer inputBuffer_接收缓冲区,用于存放从socket读到的数据方便处理数据(存放临时数据)
Buffer outputBuffer_发送缓冲区,用于存放待发出去的数据只要还没发出去的数据都放这里

三、主要方法逐一讲解

1. 构造函数 TcpConnection()

TcpConnection(EventLoop *loop, const std::string &name, int sockfd, const InetAddress& localAddr, const InetAddress& peerAddr);
  • 作用:建立连接对象,初始化相关资源
  • 内容
    • 将socket封装到socket_
    • 创建对应的Channel(事件管理器)
    • 设置Channel对应的事件和回调(读写、异常、关闭)
    • 设置socket的一些基本参数(如KeepAlive)
  • 通俗:像“注册”这条连接,让它知道什么时候可以读、写,准备好与客户端通信。

2. 析构函数 ~TcpConnection()

~TcpConnection();
  • 作用:清理资源
  • 主要是输出日志,告诉我们这条连接销毁了。

3. send()

void send(const std::string &buf);
  • 作用:对外提供接口,将数据发出去
  • 细节
    • 判断当前是否在该连接所属的loop_线程中
    • 如果在,直接调用sendInLoop()
    • 如果不在,跨线程调用runInLoop()在正确线程中发数据
  • 通俗:对外发消息的主入口,自动处理跨线程问题。

4. sendInLoop()

void sendInLoop(const void* message, size_t len);
  • 作用:实际在连接的事件循环中发送数据
  • 细节
    • 先尝试直接写(write())数据到socket
    • 如果不能全部写完,把剩余数据放到缓冲区(outputBuffer_
    • 如果缓冲区积累到一定大小(高水位),通知用户
    • 如果需要,就注册写事件,让缓冲区在可写时继续写
  • 通俗:保证数据可靠发出,遇到写不完的,缓存在缓冲区中等待下一次发出。

5. shutdown()

void shutdown();
  • 作用:优雅关闭连接
  • 改变状态,调用shutdownInLoop()在子loop中关闭写端,通知对端断开。

6. shutdownInLoop()

void shutdownInLoop();
  • 作用:实际关闭socket的写端,确保数据都发完后关闭

7. connectEstablished()

void connectEstablished();
  • 作用:连接建立时调用
  • 设置状态为已连接
  • 注册读事件到poller
  • 调用用户定义的连接建立回调

8. connectDestroyed()

void connectDestroyed();
  • 作用:连接断开
  • 设置状态为已断开
  • 移除注册的事件
  • 调用用户定义的断开回调

9. handleRead()

void handleRead(Timestamp receiveTime);
  • 作用:处理读事件(收到数据)
  • 从socket读数据到inputBuffer_
  • 调用用户定义的消息处理回调
  • 如果对端关闭,调用handleClose()
  • 发生错误,调用handleError()

10. handleWrite()

复制代码

void handleWrite();
  • 作用:处理写事件
  • 将缓冲区中的数据写出
  • 写完后,取消写事件注册
  • 调用写完成回调,通知用户

11. handleClose()

void handleClose();
  • 作用:连接关闭
  • 改变状态
  • 取消所有事件
  • 调用连接关闭回调,让上层知道连接关闭(比如Server移除)

12. handleError()

void handleError();
  • 作用:处理socket错误
  • 获取具体错误,输出日志,方便排查问题

四、总结:它像什么?

TcpConnection 就像一辆车,

  • 启动(connectEstablished):车子启动,准备好出发
  • 发消息(send, sendInLoop):车子行驶,发出货物
  • 接受消息(handleRead):收到车载信息
  • 完成发货(writeCompleteCallback):货物全发完
  • 关闭(shutdown, handleClose):停车熄火,关闭车辆
  • 异常(handleError):路上出问题,报警处理

15.Buffer类

这个Buffer类是一个高性能的内存缓冲区,为网络编程中的读写操作提供方便和高效的支持。它的作用就是临时存储接收到的数据或者待发送的数据,避免频繁的系统调用开销。下面我用通俗易懂的语言详细介绍它的成员变量和成员方法。


一、Buffer类的作用和背景

  • 在网络编程中,数据是通过读写socket的文件描述符(fd)来传输的。
  • 但socket的数据不是一次传完的(不确定接收数据的大小);需要一个缓冲区存放临时的数据。
  • Buffer类管理一块内存区域,支持高效读取和写入,方便上层代码处理网络数据。

二、成员变量详细介绍

成员变量作用/描述简单理解
static const size_t kCheapPrepend预留的前导空间(8个字节)让你可以在前面空出空间,方便以后添加内容或协议处理
static const size_t kInitialSize初始缓冲区大小(1024字节)缓冲区的起始容量大小
std::vector<char> buffer_实际存放数据的内存区域真正存储数据的容器
size_t readerIndex_读指针(读取位置的索引)读到哪个位置了,指示可以开始读取的数据起点
size_t writerIndex_写指针(写入位置的索引)数据写到哪了,指示可以开始写入数据的起点

简单理解:

这块缓冲区用一个动态数组(vector)作为底层存储空间,readerIndex_ 和 writerIndex_ 标志着数据的“开始”位置和“结束”位置。


三、静态常量定义

  • kCheapPrepend:8字节的空间,允许事先在缓冲区前面插入少量数据(比如协议头部等)
  • kInitialSize:缓冲区初始化大小1KB(1024字节)

四、成员方法详细介绍

1. 构造函数和基础访问方法

explicit Buffer(size_t initialSize = kInitialSize);
  • 初始化缓冲区容量,读写指针都指向预留空间(8字节处)

用例:

创建一个缓冲区,默认大小1024字节,提前预留一些空间。

2. readableBytes()

size_t readableBytes() const
  • 作用:计算这块缓冲区中可以读取的数据长度
  • 原理writerIndex_ - readerIndex_

比喻:就像你书架上已经摆好书的数量。


3. writableBytes()

size_t writableBytes() const
  • 作用:还能写入多少数据(还留有多大空间)
  • 原理buffer_.size() - writerIndex_

比喻:书架上剩余多少空间可以放书。


4. prependableBytes()

size_t prependableBytes() const
  • 作用:缓冲区前面可用空间(可以用来插入数据)
  • 原理readerIndex_

比喻:书架前面空余的空间,方便插入内容。


5. peek()

const char* peek() const
  • 作用:返回当前可读数据的起始地址
  • 原理begin() + readerIndex_

比喻:看见书架上待领的书的起点。


6. retrieve()

void retrieve(size_t len)
  • 作用:标记已读取的字节数,相当于“丢弃”这部分数据
  • 逻辑
    • 如果只读部分少于剩余数据,就把readerIndex_向后移动
    • 如果读取了全部剩余数据,就调用retrieveAll()复位到初始状态

比喻:拿走一部分书,调节指针。


7. retrieveAll()

void retrieveAll()
  • 作用:把缓冲区中的所有数据都“取走”,指针回到起始位置
  • 效果:使缓冲区空了,准备重新使用

8. retrieveAllAsString()

std::string retrieveAllAsString()
  • 作用:把缓冲区所有可读数据转成字符串返回
  • 用法:上层应用可以方便地处理数据

9. retrieveAsString(size_t len)

std::string retrieveAsString(size_t len)
  • 作用:取出指定长度的数据作为字符串,内部会调用retrieve(len)更新指针

10. ensureWriteableBytes()

void ensureWriteableBytes(size_t len)
  • 作用:确保缓冲区有足够空间来写入len字节
  • 逻辑
    • 如果空间不够,就调用makeSpace()扩容或移动数据到缓冲头

11. append()

复制代码

void append(const char *data, size_t len)
  • 作用:把外部数据拷贝到缓冲区
  • 过程
    • 先确保空间够
    • 拷贝数据到缓冲区末尾
    • 更新写指针

比喻:把新书放到书架上。


12. beginWrite()

复制代码

char* beginWrite()

复制代码

const char* beginWrite() const
  • 作用:返回写入位置的指针(当前可写的起点)

13. readFd()

复制代码

ssize_t readFd(int fd, int* saveErrno)
  • 作用:从文件描述符(socket)读取数据,存入缓冲区
  • 技术点
    • 使用readv()实现先尝试直接写入缓冲区
    • 如果空间不足,就用extrabuf临时缓冲,再合并到缓冲区

通俗点:像在过河,先尽量用主路(缓冲区),没有走完就用临时桥(临时缓冲区)补充。


14. writeFd()

复制代码

ssize_t writeFd(int fd, int* saveErrno)
  • 作用:把缓冲区中的数据写到文件描述符(socket)
  • 过程:直接调用write(),写出可读数据

四、私有辅助方法

1. begin()

char* begin()
  • 获取底层数组起始地址(const和非const两个版本)

2. makeSpace(size_t len)

void makeSpace(size_t len)
  • 作用
    • 如果剩余空间不够,直接resize扩大buffer
    • 如果前面有空余(因为已读取过的部分),就把剩余未读的数据移动到缓冲区头部(节省空间)

比喻:整理书架,把剩余书搬到前面,再继续放新书。


五、整体总结

  • Buffer 就像一个灵活的“组装箱”
  • 通过读指针和写指针,动态管理数据存入和取出
  • 支持高效读取(读File描述符)、写入(写到socket)
  • 提供方便的接口处理数组转字符串、扩容、移动数据
http://www.dtcms.com/a/276505.html

相关文章:

  • C++ const 关键字解析
  • windows 改用 nvm
  • 睿抗CAIP编程技能
  • AI 助力编程:Cursor Vibe Coding 场景实战演示
  • js二维数组如何变为一维数组
  • 数位动态规划详解
  • 顺序队列和链式队列
  • 淘宝商品评论API接口使用指南
  • 【C#】GraphicsPath的用法
  • Filament引擎(三) ——引擎渲染流程
  • Windows安装SSH
  • python库之jieba 库
  • 当大模型遇见毫米波:用Wi-Fi信号做“透视”的室内语义SLAM实践——从CSI到神经辐射场的端到端开源方案
  • 【Scratch】从入门到放弃(五):指令大全-九大类之运算、变量、自制积木
  • 下雨天的思考
  • 2025 XYD Summer Camp 7.10 筛法
  • Fusion: 无需路径条件的路径敏感分析
  • 端到端自动驾驶:挑战与前沿
  • Redis数据类型之set
  • 巅峰对决:文心4.5 vs DeepSeek R1 vs 通义Qwen3.0——国产大模型技术路线与场景能力深度横评
  • flowable或签历史任务查询
  • C++ Primer(第5版)- Chapter 7. Classes -001
  • 基于Java Web的二手房交易系统开发与实现
  • 利用docker部署前后端分离项目
  • 【QT】多线程相关教程
  • Linux中使用快捷方式加速SSH访问
  • 通俗范畴论13 鸡与蛋的故事番外篇
  • 2D转换之缩放scale
  • 《P2052 [NOI2011] 道路修建》
  • JavaScript:移动端特效--从触屏事件到本地存储