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


生活总是让我们遍体鳞伤,
但到后来,
那些受伤的地方一定会变成我们最强壮的地方。
-- 《老人与海》--


高并发服务器项目总结

模块关系图

项目工具模块

缓冲区模块

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

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

通用类型模块

想要实现的效果:

cpp 复制代码
Any a;
a = 123;
a = "123"
a = vector<int>(n , 0);

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

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

套接字socket模块

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

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

对外提供的接口

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

信道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。

cpp 复制代码
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模型,管理一下数据:

cpp 复制代码
    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模型内部加入一个时间轮,负责执行超时连接销毁任务,对外提供接口:

  1. Void TimerAdd(uint64_t id, int delay, Task_t cb):加入定时任务
  2. void TimerRefresh(uint64_t id):刷新定时任务
  3. void TimerCancel(uint64_t id):取消定时任务
  4. bool HasTimer(uint64_t id):判断是否有该任务

连接Connection模块

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

cpp 复制代码
    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模块,用于事件监控
  • 通用类型上下文
  • 输入/输出缓冲区
  • 新连接/处理消息/关闭连接/任意事件 回调函数
  • 连接内部连接关闭函数

内部提供的函数:

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

对外提供的接口:

  1. 基础接口:返回连接id,套接字fd...
  2. 设置回调函数接口
  3. 发送数据接口:在EventLoop中加入发送任务
  4. 关闭连接接口:在EventLoop中加入关闭连接任务
  5. 启动超时销毁接口:在EventLoop中加入启动超时销毁任务
  6. 取消超时销毁接口:在EventLoop中加入取消超时销毁任务
  7. 切换协议接口:在EventLoop中加入取消切换协议任务

Accepter模块

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

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

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

线程池模块

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

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

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

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

TcpServer模块

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

该模块管理的成员变量:

cpp 复制代码
    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;  // 任意事件触发的回调
  1. 提供给Accepterd的回调函数NewConnection,在该函数中对连接赋予一个id,并设置连接的回调函数,将该连接交给线程池中的一个线程进行监管,启动对应的超时销毁任务。
  2. 提供连接的内部释放函数RemoveConnection,销毁连接在服务器中的资源
  3. 提供给上层的设置回调函数接口
  4. 服务器开始运行:启动服务器套接字的监听,创建线程池,开启主Reactor的监控。
相关推荐
wss8 分钟前
Android 与 Unity 集成实现,思必驰 TTS 音频流处理及口型同步
android
IT技术图谱9 分钟前
【绝非标题党】Android一行代码实现网络监听
android·程序员
爱莉希雅&&&25 分钟前
DNS服务(Linux)
linux·运维·服务器
程序猿John1 小时前
Linux下创建svn库 和 svn安装与操作
linux·运维·svn
txinyu的博客1 小时前
仿modou库one thread one loop式并发服务器
运维·服务器
半新半旧1 小时前
keepalived高可用介绍
linux·服务器·网络
三品PLM系统2 小时前
三品PLM研发管理软件如何构筑制造企业全产品生命周期管理?
大数据·运维·人工智能·安全·制造
louisgeek2 小时前
Android 性能优化之界面优化
android
末央&2 小时前
【C++】vector的底层封装和实现
android·c++
希侬2 小时前
android自定义Toast样式和显示方式
android