【sylar-webserver】11 Application 模块化动态加载

39 阅读3分钟

前言

Application 模块化,全网分析的文章不多。
我觉得这个算是较为基础的 Application 设计了,实现了类似于 Spring 的设计。(业务实现和基础架构分离。)

application 模块化动态加载

关键流程 ⭐

  1. 按照约定,每个动态加载的模块,都要继承 Module 基类,实现 onLoad 模块装载,onUnload 模块卸载,onServerReady 服务器启动前。其中 onServerReady 中 负责 获取 全局变量下的 服务器,为其注册 Servlet 路由;按照约定,还需要,使用 extern C 对外暴露 两个函数符号,分别是 CreateModuleDestoryModuleCreateModule 函数负责 创建并返回 Module 基类指针指向 派生类对象。
  2. 应用初始化(Application::init())阶段,ModuleManager模块管理器 扫描指定模块路径下的所有 .so 文件;
  3. 对每个 .so 文件,通过 dlopen 加载动态库,找到 CreateModule(创建模块实例)和 DestoryModule(销毁模块实例)符号。创建模块实例,并注册到 ModuleManager 管理器 中。
  4. 在 服务器 配置完成 后,在正式 start 启动前,调用 Module 相应的onServerReady(),把相关的 Servlet 操作加载到对应的 HTTP Server 实例中。

项目里除了基础的 Module,还有 RockModule,NameServerModule。

接口

  1. Module 基类:模块接口标准化
  • 核心作用:定义所有模块必须实现的接口,规范模块的初始化、销毁、事件处理等行为。
  • 关键接口:
    • 生命周期管理:onLoad()(加载时初始化)、onUnload()(卸载时清理);
    • 服务器事件回调:onServerReady()(服务器就绪前)、onServerUp()(服务器启动后);
    • 连接事件处理:onConnect()(连接建立时)、onDisconnect()(连接断开时);
    • 消息处理:handleRequest()(处理请求消息)、handleNotify()(处理通知消息)。
  1. ModuleManager:模块全局管理器
  • 单例模式:通过 ModuleMgr 单例全局访问,负责模块的注册、卸载、查询与事件分发。
  • 核心功能:
    • 模块加载:init() 扫描配置的模块路径(默认 module/),加载所有 .so 动态库;
    • 模块管理:add() 注册模块、del() 卸载模块(触发 onUnload())、get() 按名称查询模块;
    • 事件分发:连接建立 / 断开、服务器状态变更等事件,通过 onConnect()onServerUp() 等方法同步通知所有模块;
    • 类型分类:按模块类型(type)维护映射表,支持按类型遍历模块(foreach())。
  • 线程安全:使用读写锁(RWMutex)保护模块容器(m_modulesm_type2Modules),确保多线程环境下的安全访问。
  1. Library:动态库加载工具
  • 核心作用:封装 dlopendlsym 等系统调用,实现动态库的加载与模块实例的创建。
  • 加载流程: a. 通过 dlopen 加载指定路径的 .so 动态库,获取句柄; b. 查找动态库中导出的 CreateModule(创建模块实例)和 DestoryModule(销毁模块实例)符号; c. 调用 CreateModule 生成 Module 实例,通过自定义删除器(ModuleCloser)管理生命周期(销毁时调用 DestoryModuledlclose 释放句柄); d. 加载成功后自动读取配置文件,更新模块相关配置。

dlopen 加载过程⭐

103.影石一面,第5题:dlopen是什么?如何使用

  1. 查找库文件:绝对路径 > 环境变量 > 项目下的缓存库 > 默认系统目录
  2. 加载到内存:内核通过 mmap.so 文件的代码段,数据段映射到当前进程的虚拟地址空间
  3. 重定向:
    • 动态链接库 ld.so 解析 .so 中的符号引用,把函数地址,全局变量地址修正到正确的位置。
    • 如果使用RTLD_LAZY,符号地址在第一次调用时才解析
  4. 指向初始化代码
    • a. 调用库里的 .init_array 段(构造函数),完成全局对象初始化。
  5. 返回句柄,用户通过返回的 void* 句柄,同 dlsym 查找符号地址,调用函数。