云计算百科
云计算领域专业知识百科平台

【项目日记】高并发服务器项目总结

在这里插入图片描述

生活总是让我们遍体鳞伤,

但到后来,

那些受伤的地方一定会变成我们最强壮的地方。

— 《老人与海》–


高并发服务器项目总结

  • 模块关系图
  • 项目工具模块
    • 缓冲区模块
    • 通用类型模块
    • 套接字socket模块
    • 信道Channel模块
    • 多路转接Poller模块
  • Reactor模块
    • 时间轮TimeWheel模块
  • 连接Connection模块
  • Accepter模块
  • TcpServer模块

模块关系图

在这里插入图片描述

项目工具模块

缓冲区模块

底层使用字符容器储存数据,通过两个指针:读偏移和写偏移管理空间:

  • 对于写入:确保空间足够,在写偏移后拷贝数据,写偏移向后移动
  • 对于读取:确保可读数据足够,在读偏移后读取数据,读偏移向后移动
  • 空间不足时及时扩容

通用类型模块

想要实现的效果:

Any a;
a = 123;
a = "123"
a = vector<int>(n , 0);

为了实现这种效果,首先Any类肯定不能带有模版参数,不然就不能自由转换了!

  • 可以在内部设计一个内部类placeholder(继承holder父类),它是一个模板类,是实际储存数据的对象;
  • 当我们创建了一个Any对象时,会创建一个父类指针,管理placeholder对象
  • 通过模版函数,我们可以实现对placeholder对象赋值。
  • 可以通过交换placeholder对象管理的资源实现自由赋值。

套接字socket模块

这个模块对外提供一键创建服务端套接字和客户端套接字的方法(是对内部接口的一个封装):

  • 创建套接字接口:以数据流的方式创建一个套接字文件
  • 绑定端口信息
  • 进入监听模式
  • 发起连接:组织目标端口信息,进行申请连接
  • 启动地址重用:可以快速重启服务,跳过系统释放端口的时间;允许多个 socket 监听相同的地址和端口;避免地址已在使用错误。
  • 设置非阻塞读取

对外提供的接口

  • 构建服务端套接字:创建套接字,设置为非阻塞,将地址与端口设置为可重用,绑定地址信息,进行监听。
  • 构建客户端套接字:创建套接字,连接服务器
  • 发送数据
  • 接收数据
  • 信道Channel模块

    该模块是用来管理一个文件描述符的,对该描述符的事件进行监控,触发事件就调用对应的回调函数:

    • 当前需要监控的事件集
    • 当前连接触发的事件集
    • 管理该文件描述符的Reactor模型(EventLoop)
    • 可读事件触发的回调函数
    • 可写事件触发的回调函数
    • 错误事件触发的回调函数
    • 断开事件触发的回调函数
    • 任意事件触发的回调函数

    提供一系列接口帮助我们管理对该文件描述符的监控。触发事件通过HandleEvent()函数进行处理。

    多路转接Poller模块

    该模块是对epoll接口的二次封装,管理一系列的文件描述符。内部储存文件描述符与其对应的信道Channel; 可以通过信道对epoll中监控的文件描述符进行更新监控事件集 提供一个开始监控接口Poll,该函数中阻塞式等待事件就绪。得到就绪事件就进行将就绪事件更新到对应的信道中,然后将信道放入活跃队列中等待上层处理。

    Reactor模块

    时间轮TimeWheel模块

    时间轮模块是用来执行定时任务的,项目中可以用来管理超时连接,及时进行释放操作。 为了可以实现定时执行任务,首先需要对封装出一个任务对象Timer:

    • 任务ID uint64_t _id :用来标识任务
    • 超时时间 uint32_t _timeout;
    • 是否被取消:bool _canceled; 这样取消任务时,通过哈希表找到对应任务,设置该标志位就好了,效率高!
    • 回调任务 _task_cb void()类型
    • 释放操作函数 _release :用于删除TimeWheel保存的定时器对象信息
    • 任务释放时执行回调函数

    时间轮TimeWheel的本质是一个数组,一个指针定时移动,到哪里就释放该位置的所有任务Timer。

    class TimeWheel
    {
    using TaskPtr = std::shared_ptr<Timer>;
    using WeakPtr = std::weak_ptr<Timer>; // 辅助shared_ptr 不会增加引用计数
    private:
    int _capacity; // 最大容量 表盘最大数量(默认60秒)
    int _tick; // 移动表针
    std::vector<std::vector<TaskPtr>> _wheel; // 时间轮
    std::unordered_map<uint64_t, WeakPtr> _timers; // 定时任务对象哈希表

    int _timerfd; // 定时器fd
    EventLoop *_loop; // 对应的Eventfd
    std::unique_ptr<Channel> _timer_channel; // 管理_timerfd的channel类!
    //…
    }

    这里使用智能指针进行管理,方便操作。引用计数归零时自动释放。设置一个timefd,系统每1s都会想其中写入一个1。读取到数据,就移动指针,释放该时间的定时任务指针。 EventLoop模块 该模块是真正的Reactor模型,管理一下数据:

    std::thread::id _event_id; // 线程ID
    int _eventfd; // eventfd 用于通知事件
    std::unique_ptr<Channel> _event_channel; // 管理Event事件的Channel对象
    Poller _poller; // epoll模型

    std::vector<Functor> _tasks; // 任务池
    std::mutex _mtx; // 互斥锁保护线程

    TimeWheel _timer_wheel; // 时间轮

    该模块的执行逻辑为:

    • 通过EventLoop模块通过的接口可以添加管理的信道(文件描述符)
    • 启动监控后,对epoll中的文件描述符进行事件监控。
    • Poller模块Poll函数中返回就绪的活跃信道集。
    • 对每个活跃信道执行handleEvent处理就绪事件
    • 对于外部设置的任务,需要保证是在该eventloop所在线程内部执行,如果不是放入任务池中等待执行。

    EventLoop模块与TimeWheel模块整合 Reactor模型内部加入一个时间轮,负责执行超时连接销毁任务,对外提供接口:

  • Void TimerAdd(uint64_t id, int delay, Task_t cb):加入定时任务
  • void TimerRefresh(uint64_t id):刷新定时任务
  • void TimerCancel(uint64_t id):取消定时任务
  • bool HasTimer(uint64_t id):判断是否有该任务
  • 连接Connection模块

    连接模块是对上面模块的整体整合,具有以下参数:

    uint64_t _conn_id; // connection连接ID
    Socket _socket; // 管理的套接字
    int _sockfd; // 套接字fd
    EventLoop *_loop; // connection连接关联的EventLoop对象
    Any _context; // 上下文数据
    Channel _channel; // 管理连接事件
    Buffer _in_buffer; // 输入缓冲区 存放Socket中读取的数据
    Buffer _out_buffer; // 输出缓冲区 存放要发送给对端的数据
    bool _enable_active_release; // 是否开启超时销毁 默认是false
    ConnStatu _statu; // Connection连接状态

    // 5 个 回调函数 — 注意使用智能指针 防止在执行任务之前Connection销毁

    using ConnectedCallBack = std::function<void(const PtrConn &)>; // 连接时进行的回调函数
    using MessageCallBack = std::function<void(const PtrConn &, Buffer *)>; // 处理数据时的回调函数
    using ClosedCallBack = std::function<void(const PtrConn &)>; // 关闭连接时的回调函数
    using AnyEventCallBack = std::function<void(const PtrConn &)>; // 处理任意事件时的回调函数

    ConnectedCallBack _conn_cb; // 连接回调函数类型
    MessageCallBack _message_cb; // 处理时回调函数
    ClosedCallBack _closed_cb; // 关闭阶段的回调
    AnyEventCallBack _event_cb; // 任意事件触发的回调

    // 还需要组件内的连接关闭回调 因为服务器组件内会把所有的连接管理起来 一旦某个连接关闭 就应该从管理的地方移除自己的信息!
    ClosedCallBack _event_closed_cb;

    • 该连接管理的底层套接字
    • 管理该套接字的信道:用于处理新连接事件
    • 连接关联的EventLoop模块,用于事件监控
    • 通用类型上下文
    • 输入/输出缓冲区
    • 新连接/处理消息/关闭连接/任意事件 回调函数
    • 连接内部连接关闭函数

    内部提供的函数:

  • 首先是该连接内部遇到事件的函数,作为信道的回调函数
    • 读事件回调函数
    • 写事件回调函数
    • 关闭事件回调
    • 错误事件回调
  • 线程内发送数据函数
  • 线程内关闭连接函数
  • 线程内启动超时管理函数:将该连接的释放函数Release放入EventLoop的时间轮中
  • 线程内取消超时管理函数
  • 线程内更新回调函数(协议)
  • 线程内释放函数ReleaseInLoop(真正的释放函数):修改连接状态,取消定时任务,执行用户设置的关闭连接回调函数,通过内部释放回调函数释放在连接在服务器内的资源。
  • 对外提供的接口:

  • 基础接口:返回连接id,套接字fd…
  • 设置回调函数接口
  • 发送数据接口:在EventLoop中加入发送任务
  • 关闭连接接口:在EventLoop中加入关闭连接任务
  • 启动超时销毁接口:在EventLoop中加入启动超时销毁任务
  • 取消超时销毁接口:在EventLoop中加入取消超时销毁任务
  • 切换协议接口:在EventLoop中加入取消切换协议任务
  • Accepter模块

    该模块是对监听套接字进行管理的:

    Socket _socket; // 套接字对象
    EventLoop *_loop; // 对监听套接字进行事件监控
    Channel _channel; // 用于对管理监听套接字的事件
    using AcceptCallBack = std::function<void(int)>;
    AcceptCallBack _accept_callback;

    不同于不同的套接字文件,监听套接字只需要读取事件的回调函数,从监听套接字中读取出新连接的fd,然后对该fd执行获取连接的回调函数。

    线程池模块 我们先对线程进行封装,使其包含EventLoop模块。线程中需要执行的函数就是创建一个EventLoop,然后执行Start监控。

    线程池对若干个线程进行管理:

    int _thread_size; // 从属线程数量
    int _next_idx; // 线程索引
    std::vector<LoopThread *> _threads; // 管理从属线程
    std::vector<EventLoop *> _loops; // 管理从属Reactor
    EventLoop *_baseloop; // 主Reactor

    每次取出一个线程对外提供时,只需提供线程对应的EventLoop即可

    TcpServer模块

    这个模块就是最终的服务器模块。主Reactor对监听套接字的进行管理,获取到新连接后就将连接交给一个线程中的从属Reactor进行监控。进而实现高并发的服务器!

    该模块管理的成员变量:

    uint64_t _conn_id; // 自增长的连接ID;
    int _port; // 绑定的端口号
    EventLoop _baseloop; // 主Reactor模型
    Acceptor _acceptor; // 监听套接字
    LoopThreadPool _pool; // 从属线程池
    std::unordered_map<uint64_t, PtrConn> _conns; // 管理连接的哈希表
    int _timeout; // 超时时间
    bool _enable_active_release; // 是否开启超时销毁 默认是false

    // 4种 回调函数 — 注意使用智能指针 防止在执行任务之前Connection销毁
    using ConnectedCallBack = std::function<void(const PtrConn &)>; // 连接时进行的回调函数
    using MessageCallBack = std::function<void(const PtrConn &, Buffer *)>; // 处理数据时的回调函数
    using ClosedCallBack = std::function<void(const PtrConn &)>; // 关闭连接时的回调函数
    using AnyEventCallBack = std::function<void(const PtrConn &)>; // 处理任意事件时的回调函数

    ConnectedCallBack _conn_cb; // 连接回调函数类型
    MessageCallBack _message_cb; // 处理时回调函数
    ClosedCallBack _closed_cb; // 关闭阶段的回调
    AnyEventCallBack _event_cb; // 任意事件触发的回调

  • 提供给Accepterd的回调函数NewConnection,在该函数中对连接赋予一个id,并设置连接的回调函数,将该连接交给线程池中的一个线程进行监管,启动对应的超时销毁任务。
  • 提供连接的内部释放函数RemoveConnection,销毁连接在服务器中的资源
  • 提供给上层的设置回调函数接口
  • 服务器开始运行:启动服务器套接字的监听,创建线程池,开启主Reactor的监控。
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【项目日记】高并发服务器项目总结
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!