Reactor模型高并发服务器——01_项目准备

请添加图片描述

✨✨欢迎来到T_X_Parallel的博客!!
    🛰️博客主页:T_X_Parallel
    🛰️项目代码仓库:Reactor模型高并发服务器项目代码仓库(随博客更新)
    🛰️专栏 : Reactor模型高并发服务器项目
    🛰️欢迎关注:👍点赞🙌收藏✍️留言

项目环境:vscode、wsl2(Ubuntu22.04)

技术栈:C/C++、C++11、C++17、STL、HTTP、TCP、HTML

1. 前言

正如上一个内容中所讲的,该项目中有很多需要设置的回调函数还有线程池中的执行函数,那么在设计的时候为了不用考虑有哪些任务处理方式,设计成任务由使用者传进来,这时就可以使用到C++标准库中的bind()函数来将函数与参数进行绑定传进来

该项目中还有一个超时释放功能,这个功能类似于一种定时任务,时间一到就执行指定任务,那么在正式做项目前需要实现一个定时任务模块

该项目中的HTTP协议支持模块中肯定是需要进行HTTP请求解析操作,该操作需要进行对字符串的提取与匹配,这时使用正则表达式匹配可以处理效率变高,模块实现起来更加灵活

该项目中的Connection模块是对连接进⾏管理,该模块会涉及到应⽤层协议的处理,因此在 Connection中需要设置协议处理的上下⽂来控制处理节奏。由于应⽤层协议千千万,为了降低耦合度,这个协议接收解析上下⽂就不能有明显的协议倾向,它可以是任意协议的上下⽂信息,因此就需要⼀个通⽤类型来保存各种不同的数据结构,则在正式做项目之前需要了解通用类的基本知识


2. bind函数

在这里插入图片描述

概念

官方文档中对该函数的概括就是:Bind function arguments

其实可以将bind函数看作一个通用的函数适配器,参数是一个函数对象以及函数的各项参数,返回值是一个新的函数对象,但这个新函数对象已经被设置过参数,相当于运行时时调用了一个固定参数的原函数

在进行绑定参数的时候可以不绑定所有参数,即设置一些参数为新函数对象的参数,而方法就是在绑定时参数位置给予std::placeholders::_1、_2...即可实现

使用案例

使用一:

#include <iostream>
#include <functional>
#include <unistd.h>
#include <string>
#include <vector>

class Test
{
public: 
 Test()
 {
     std::cout << "构造" << std::endl;
 }
 ~Test()
 {
     std::cout << "析构" << std::endl;
 }
};

void Delete(Test *t, int n)
{
 std::cout << n << std::endl;
 delete t;
}

int main()
{
 Test* t = new Test();
 std::function<void(int)> cd = std::bind(Delete, t, std::placeholders::_1);
 cd(10);
 while(1);
 return 0;
}

运行结果

g++ test_bind.cc -o bind -std=c++11
./Bind 
构造
10
析构

该使用是将Test类进行删除的操作,并且也预留了一个参数

使用二:

#include <iostream>
#include <functional>
#include <unistd.h>
#include <string>
#include <vector>

void Print(const std::string& s)
{
 std::cout << s << std::endl;
}

int main()
{
 std::vector<std::function<void()>> task_pool;
 task_pool.push_back(std::bind(Print, "xiaomi"));
 task_pool.push_back(std::bind(Print, "su7"));
 task_pool.push_back(std::bind(Print, "ultra"));

 for (auto& e : task_pool)
 {
     e();
 }
 return 0;   
}

运行结果

g++ test_bind.cc -o Bind -std=c++11
./Bind 
xiaomi
su7
ultra

该使用案例就是构建了一个任务池,然后再逐一执行任务池中的任务

上面两种bind函数使用方式在项目中都会使用到,所以需要深刻理解该函数的使用方式


3. 定时任务

概念

在该项目所要实现的高并发服务器中,需要考虑一个问题,就是连接超时如何处理的问题,解决这些超时连接来将资源释放掉,这里就涉及到定时任务,即超时时间一到指定时间即刻释放该链接,下面有两种思路来实现定时任务

Linux定时器

在这里插入图片描述

这几个接口是Linux系统提过的定时器,作用就是该定时器会在每次超时时,自动给timerfd描述符写入8字节数据,数据为上次读取数据到当前读取数据期间超时了多少次,下面是各函数参数及返回值的介绍

int timerfd_create(int clockid, int flags)

在这里插入图片描述

clockid: CLOCK_REALTIME—基于系统实时时间,如果修改了系统时间会出问题

​ CLOCK_MONOTONIC—基于从开机到当前的时间,是一种相对时间

flags:0—默认阻塞属性

返回一个文件描述符timerfd


int timerfd_settime(int fd, int flags, 
                    const struct itimerspec *new_value, 
                    structitimerspec *old_value);

在这里插入图片描述

fd:timerfd_create返回的文件描述符

flags:0—相对时间 1—绝对时间(默认设置为0即可)

new_value:用于设置定时器的新超时时间

old_value:设置接收原来的超时时间

struct timespec {
    time_t tv_sec;                /* 秒 */
    long   tv_nsec;               /* 纳秒 */
};
struct itimerspec {
    struct timespec it_interval;  /* 第一次之后的超时时间间隔*/
    struct timespec it_value;     /* 第一次超时时间间隔 */
};

返回值为一个整数类型,设置成功返回1,失败则返回0


int timerfd_gettime(int fd, struct itimerspec *curr_value);

在这里插入图片描述

fd:timerfd_create返回的文件描述符

curr_value:作为返回参数,返回指定定时器的设置(具体数据结构如上)

返回值为一个整数类型,设置成功返回1,失败则返回0

下面是接口使用示例

#include <iostream>
#include <unistd.h>
#include <sys/timerfd.h>
#include <ctimer>

int main()
{
    int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);

    struct itimerspec itm;
    itm.it_interval.tv_nsec = 0;
    itm.it_interval.tv_sec = 5;
    itm.it_value.tv_nsec = 0;
    itm.it_value.tv_sec = 5;
    timerfd_settime(timerfd, 0, &itm, NULL);
    time_t start = time(NULL);
    while (1)
    {
        uint64_t res;
        int ret = read(timerfd, &res, sizeof(res));
        if (ret < 0)
            return -1;

        std::cout << res << " " << time(NULL) - start << std::endl;
    }
    return 0;
}

运行结果

g++ test_timer.cc -o Timer -std=c++11
./Timer 
1 5
1 10
1 15
1 20
    

该示例中为每隔5秒触发一次定时器超时,否则就会阻塞在read那里,基于这个例子,则可以实现每隔固定时间检测哪些连接超时,然后去释放掉

时间轮

使用Linux内置定时器实现定时任务执行会存在一个问题,每次超时都要将所有连接遍历一遍,如果有上万个连接,效率会非常非常低

这时就会想到使用小根堆存储所有连接,那么堆上面的大概率就是超时的连接,逐个释放直至没有超时连接位置,这样可以有效提高处理效率,但是又产生一个问题,如果某些连接在快超时的时候请求,那么会进行超时刷新,那么这个方法就不太能实现了。下面的时间轮方法就能很好解决上面这两个问题

时间轮思想来自于钟表,如果定一个9点闹钟,那么时针走到9的时候就会提醒,说明9点到了

按照钟表的形象,可以创建一个固定长数组,且有一个指针,一开始指向数组起始位置,然后指针每秒走一步,走到哪里,哪里的任务就执行,但是同一时刻可能会有很多定时任务,那么可以使用二维数组,每个位置都是一个数组,那么就可以存放多个定时任务

在这里插入图片描述

当然上面的设计也同样存在问题,如果一个任务需要1小时29分钟39秒后执行,那么这个数组大小会很长,为了解决这个问题可以设计多时间轮,存在时针轮、分针轮、秒针轮,每个针的运动规则和时钟的运行规则相同。但是当前项目中不需要这么麻烦的设计,定时任务通常时间很短,所以单时间轮就够用

然后就是自动执行任务的实现方法,当前的设计是时间到了要主动执行定时任务,为了方便需要改进设计,让任务自动执行,这时会想到类的析构函数,当一个类释放时肯定会执行析构函数,如果将定时任务放进一个类的析构函数中,该类的对象放入时间轮中,只要指针到哪,就清空那里的所有对象,即可执行所有类中的定时任务,即可完成自动执行的目的

基于这个想法,还要面对一个问题,就是解决刷新定时任务的问题,这时就会想到智能指针中的shared_ptr,多个同一对象的shared_ptr内有一个共享计数器,当所有shared_ptr释放后才会将该对象释放并执行析构函数,那么只需在需要刷新定时任务时在指定位置再放入一个shared_ptr即可。

但是直接对同一个对象直接构造多个shared_ptr智能指针,这些指针中的共享计数器不会同步,可以使用一个shared_ptr进行拷贝构造多个shared_ptr智能指针,这些指针中的计数器才能共享,但是这就会存在一个问题,最开始的指针需要额为存储起来,为了能够找到,应该使用哈希表存储所有任务的shared_ptr智能指针,但是最后这哈希表中的shared_ptr如何释放掉,就算想到方法释放掉,那么应该也很麻烦。这里其实可以在哈希表中存储由第一个shared_ptr拷贝构造出来的weak_ptr,而weak_ptr的存在不会影响其他shared_ptr内的共享计时器,也能通过weak_ptrlock()函数构造出新的共享计数器的shared_ptr,而哈希表中的weak_ptr的释放可以放在对应的对象的析构函数与定时任务一起执行,该释放函数可以在构造对象的时候传入函数中即可,这样设计思路就清晰了,可以见下图

在这里插入图片描述

#include <iostream>
#include <vector>
#include <unordered_map>
#include <sys/timerfd.h>
#include <ctime>
#include <unistd.h>
#include <functional>
#include <memory>

#define MAX_TIMEOUT 60
typedef std::function<void()> Function;

class TimerTask
{
    private:
    uint64_t _id;    // 任务ID
    bool _canceled;  // 任务状态
    int _timeout;    // 设置的超时时间
    // 定时任务回调函数
    Function _taskcallback;  
    // 将任务完全从时间轮中释放掉的回调函数,就是释放掉哈希表中的weak_ptr 
    Function _releasecallback;

    public:
    // 构造函数
    TimerTask(uint64_t id, int timeout)
        : _id(id), _canceled(false), _timeout(timeout)
        {
        }
    // 析构函数,条件满足再执行定时任务和释放的回调函数
    ~TimerTask()
    {
        if (_releasecallback)
            _releasecallback();
        if (_taskcallback && !_canceled)
            _taskcallback();
    }

    public:
    // 设置定时任务回调函数
    void SetTaskCallBack(const Function &taskcallback)
    {
        _taskcallback = taskcallback;
    }
    // 设置释放的回调函数
    void SetReleaseCallBack(const Function &releasecallback)
    {
        _releasecallback = releasecallback;
    }
    // 获取定时任务的超时时间
    int GetTimeOut()
    {
        return _timeout;
    }
    // 取消定时任务
    void TaskCancel()
    {
        _canceled = true;
    }
};

typedef std::shared_ptr<TimerTask> TimerTask_SharedPtr;
typedef std::weak_ptr<TimerTask> TimerTask_WeakPtr;

class Timer
{
    private:
    int _tick; // 指针 
    int _cap;  // 时间轮容量
    // 二维数组的时间轮
    std::vector<std::vector<TimerTask_SharedPtr>> _clock;
    // 存储对象的weak_ptr的哈希表
    std::unordered_map<uint64_t, TimerTask_WeakPtr> _timertaskhash;

    public:
    // 构造函数
    Timer()
        : _tick(0), _cap(MAX_TIMEOUT), _clock(_cap)
        {
        }

    public:
    // 判断某定时任务是否存在
    bool ExistTimerTask(uint64_t id)
    {
        auto iter = _timertaskhash.find(id);
        if (iter == _timertaskhash.end())
            return false;
        return true;
    }
    // 添加定时任务
    void AddTimerTask(uint64_t id, uint64_t timeout, const Function &cb)
    {
        if (timeout > _cap || timeout < 0)
            return;

        TimerTask_SharedPtr timertask(new TimerTask(id, timeout));
        timertask->SetTaskCallBack(cb);
        timertask->SetReleaseCallBack(std::bind(&Timer::DeleteHashKey, this, id));
        _clock[(_tick + timeout) % _cap].push_back(timertask);
        _timertaskhash[id] = TimerTask_WeakPtr(timertask);
    }
    // 刷新定时任务
    void RefreshTimerTask(uint64_t id)
    {
        auto iter = _timertaskhash.find(id);
        if (iter == _timertaskhash.end())
            std::cout << "No Exist " << id << " Task" << std::endl;
        TimerTask_SharedPtr newtimertask = iter->second.lock();
        int delay_pos = newtimertask->GetTimeOut() + _tick;
        _clock[delay_pos].push_back(newtimertask); 
    }

    // 取消定时任务
    void CancelTimerTask(uint64_t id)
    {
        auto iter = _timertaskhash.find(id);
        if (iter == _timertaskhash.end())
            std::cout << "No Exist " << id << " Task" << std::endl;
        iter->second.lock()->TaskCancel();
    } 

    // 指针移动
    void MoveTick()
    {
        _tick = (_tick + 1) % _cap;
        _clock[_tick].clear();
    }

    private:
    // 传给TimerTask做释放回调,删除哈希表中该对象的weak_ptr
    void DeleteHashKey(uint64_t id)
    {
        auto iter = _timertaskhash.find(id);
        if (iter != _timertaskhash.end())
            _timertaskhash.erase(iter);
    }

};

具体实现就在上面,博主也不逐一讲解了,关键注释也加上,希望读者可以理解上面的代码逻辑,如有不懂可以在评论区留言,博主看到就会针对性回答

下面进行测试

//测试代码
class Test
{
public:
    Test()
    {
        std::cout << "构造" << std::endl;
    }
    ~Test()
    {
        std::cout << "析构" << std::endl;
    }
};

void Delete(Test *t)
{
    delete t;
}

int main()
{
    Test *t = new Test();

    ns_timer::Timer timer;
    int taskid = 2;
    int timeout = 5;
    timer.AddTimerTask(taskid, timeout, std::bind(Delete, t));

    int curtime = 0;
    int i = 4;
    while (i--)
    {
        std::cout << curtime++ << std::endl;
        sleep(1);
        timer.MoveTick();
    }
    // timer.RefreshTimerTask(taskid);
    // timer.CancelTimerTask(taskid);

    // std::cout << timeout << "秒后执行任务2" << std::endl;
    while (1)
    {
        std::cout << curtime++ << std::endl;
        sleep(1);
        timer.MoveTick();
        if (timer.ExistTimerTask(taskid) == false)
        {
            std::cout << "定时任务已执行完成或取消" << std::endl;
            break;
        }
    }

    return 0;
}


运行结果

g++ test_timer.cc -o Timer -std=c++11
./Timer
构造
0
1
2
3
4
析构
定时任务已执行完成或取消

4. 正则表达式匹配

概念

正则表达式描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种字串、用来将匹配的字串替换或者从某个串取出符合某个条件的子串等用途

正如前言所说,正则表达式的使用,可以使HTTP请求的解析更加简单(这里指的是会让模块设计简单,不是提高效率,但实际上效率低于直接字符串处理)

正则表达式 – 语法 | 菜鸟教程这个网站有许多正则表达式的使用教程以及示例,博主只在下面先测试C++正则表达式匹配接口,正则表达式使用很简单的示例,如果需要学习正则表达式,这个网站非常推荐

示例

#include <iostream>
#include <string>
#include <regex>

int main()
{
 std::string str = "/xiaomi su7ultra 2025";
 std::regex r("/xiaomi (.*) (\\d+)" );
 std::smatch matches;
 bool ret = std::regex_match(str, matches, r);
 if (ret == false)
     return -1;
 for (const auto &s : matches)
 {
     std::cout << s << std::endl;
 }
 return 0;
}

/xiaomi : 匹配字符串的开头部分,要求以 /xiaomi 开头。

(.*): 匹配任意字符(除换行符外)0次或多次,并将其捕获到第一个捕获组中。

(\\d+): 匹配一个或多个数字字符,并将其捕获到第二个捕获组中。

运行结果

g++ test_regex.cc -o Regex -std=c++11
./Regex
/xiaomi su7ultra 2025
su7ultra
2025

结果表面匹配结果的第一个肯定是整个原字符串,后面就是需要提取出来的字符串


5. 通用类

上一篇博客和该博客前面提及过,该项目需要实现对应用层协议的支持,而在Connection模块中需要对连接进行管理,则最终避免不了需要涉及应用层协议的处理,因此在该模块中需要设置处理的上下文来控制处理,但是应用层协议数量很多,为了降低耦合度,协议接收解析上下文就不能有明显的协议倾向。为了设计成任意协议的上下文信息,因此就需要一个通用类型来保存各种不同的数据结构

在C语言中,通用类型可以使用void*来实现,而在C++中,boost库和C++17标准库中提供了通用类型any,但如果考虑代码的可移植性,尽量减少第三方库的依赖,则应该使用C++17标准库中的any或者自己实现一个any类。因为这个any通用类设计不复杂,所以下面是简单的实现过程

这个实现只是能够去了解这个通用类的设计思想,博主认为这个实现挺有学习价值的,所以自己试着实现出来,但是项目中大概率只会使用C++17标准库中的any类

下面的实现只详细讲述核心思想,就不逐一就实现过程讲解,具体看博主实现的代码,代码中包含主要注释,如果任何问题,请在评论区留言,博主会第一时间解答

首先通用类型any类肯定不能是一个模板类,否则编译Any<int> aAny<float> b的时候,需要传类型作为模板参数,也就等于使用的时候需要确定器类型,这是不符合通用类型的特性的,而且定义any对象的时候是不知道具体的协议类型的,因此无法传递类型作为模板参数

因此可以在any类中设计一个模板容器holder类,可以保存各种类型数据,但是由于any不知道保存什么类型数据,则Any类中无法定义这个holder类的对象或指针,所以可以使用C++中的特性继承,在Any类中再定义一个基类placeholder,让holder继承placeholder,Any类保存父类指针即可,当不需要保存数据的时候,就不需要传递类型,保存数据时,只需new一个带有模板参数的子类holder对象保存数据,然后让Any类中的父类指针指向这个子类对象即可

在这里插入图片描述

实现代码

class Any
{
    public:
    // 默认构造函数,初始化容器指针为空
    Any()
        : _container(nullptr)
        {
        }

    // 模板构造函数,用于存储任意类型的值
    template <typename T>
    Any(const T &value)
    : _container(new holder<T>(value))
    {
    }

    // 拷贝构造函数,深拷贝容器内容
    Any(const Any &other)
        : _container(other._container ? other._container->clone() : nullptr)
        {
        }

    // 模板赋值运算符,使用临时对象交换内容
    template <typename T>
    Any &operator=(const T &value)
    {
        Any(value).Swap(*this);
        return *this;
    }

    // 拷贝赋值运算符,交换内容
    Any &operator=(Any other)
    {
        this->Swap(other);
        return *this;
    }

    // 析构函数,释放动态分配的内存
    ~Any()
    {
        if (_container != nullptr)
            delete _container;
    }

    public:
    // 交换两个 Any 对象的内容
    Any &Swap(Any &other)
    {
        std::swap(_container, other._container);
        return *this;
    }

    // 获取存储的值,类型不匹配时断言失败
    template <typename T>
    T *Get()
    {
        assert(typeid(T) == _container->type());
        return &(((holder<T> *)_container)->_val);
    }

    private:
    // 抽象基类,定义接口
    class placeholder
    {
        public:
        virtual ~placeholder() {}
        virtual const std::type_info &type() = 0;
        virtual placeholder *clone() = 0;
    };

    // 模板派生类,用于存储具体类型的值
    template <typename T>
    class holder : public placeholder
    {
        public:
        holder(const T &val)
            : _val(val)
            {
            }

        // 返回存储值的类型信息
        const std::type_info &type()
        {
            return typeid(T);
        }

        // 克隆当前对象
        placeholder *clone()
        {
            return new holder(_val);
        }

        public:
        T _val; // 存储的值
    };

    private:
    placeholder *_container; // 指向存储值的容器指针
};

测试代码

#include <iostream>
#include "any.hpp"
#include <unistd.h>

// 定义一个测试类
class Test
{
public:
    // 构造函数
    Test() { std::cout << "构造" << std::endl; }
    // 拷贝构造函数
    Test(const Test &t) { std::cout << "拷贝" << std::endl; }
    // 析构函数
    ~Test() { std::cout << "析构" << std::endl; }
};

int main()
{
    // 使用自定义的 ns_any::Any 类
    ns_any::Any a;
    {
        Test t; // 创建一个 Test 对象
        a = t;  // 将 Test 对象赋值给 Any 对象
    } // 作用域结束,Test 对象被析构

    a = 10; // 将整数赋值给 Any 对象
    int *pa = a.Get<int>(); // 获取整数指针
    std::cout << *pa << std::endl; // 输出整数值

    a = std::string("nihao"); // 将字符串赋值给 Any 对象
    std::string *ps = a.Get<std::string>(); // 获取字符串指针
    std::cout << *ps << std::endl; // 输出字符串值

    // 无限循环,防止程序退出
    while (1)
        sleep(1);

    return 0;
}

测试结果

g++ test_any.cc -o Any -std=c++11
./Any 
构造
拷贝
析构
析构
10
nihao

当然,因为该项目中博主主要是使用C++17标准库中的any类,所以也对该类进行使用测试

在这里插入图片描述

示例

#include <iostream>
#include <string>
#include <any>
int main()
{
    // 使用 std::any 的示例代码(已注释掉)
    std::any a;
    a = 10;
    int *pi = std::any_cast<int>(&a);
    std::cout << *pi << std::endl;

    a = std::string("hello");
    std::string *ps = std::any_cast<std::string>(&a);
    std::cout << *ps << std::endl;

    return 0;
}

运行结果

g++ test_any.cc -o Any -std=c++17
./Any 
10
hello

下一篇博客内容就是正式开始实现项目模块了,敬请期待


请添加图片描述

专栏:Reactor模型高并发服务器项目
项目代码仓库:Reactor模型高并发服务器项目代码仓库(随博客更新)
都看到这里了,留下你们的珍贵的👍点赞+⭐收藏+📋评论吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

T_X_Parallel〆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值