【项目篇】高并发服务器 - 从 Buffer 到 TcpServer 构建高并发服务器引擎

文章目录

Buffer模块:缓冲区模块

功能:存入数据,提取数据

实现思想:

(1)实现缓冲得有一块内存空间,采用vector来实现。vector底层使用的就是一块线性的内存空间。

(2)要素:

① 默认的空间大小

②当前的读取数据位置

③当前的写入数据位置

(3)操作:

①写入数据

当前写入位置指向哪里,就从哪里开始写入。

如果当前写入时后面的空间不够了,有两种情况:

1.考虑整体缓冲区空闲位置够不够,读位置将会向后移动,前面的位置有空闲空间。

足够:将数据移动到其实位置即可

不够:扩容,从当前写位置开始扩容足够大小

数据一旦写成功,当前的写位置就要向后偏移

②读取数据

当前的读取位置指向哪里,就从哪里读起。前提是有数据可读

可读数据大小:当前写入位置减去当前读取位置

实现一个缓冲区类该如何设计:

cpp 复制代码
class Buffer
{
private:
	vector<char> _buffer;
 	//位置是一个相对偏移量,而不是绝对地址
	uint64It _read_idx; //相对读偏移量
	uint64It _write_idx; //相对写偏移量
public:
	//1.获取当前写位置地址
	//2.确保可写位置足够(足够移动,不够扩容)
	//3.获取前沿空闲空间大小
	//4.获取后沿空闲空间大小(可读位置以前的空间)
	//5.将写位置向后移动指定长度
	//6.获取当前读位置的地址
	//7.获取可读数据大小
	//8.将读位置向后移动指定长度
	//9.清理功能
};

Buffer模块的代码设计:

cpp 复制代码
#include <iostream>
#include <cstring>
#include <vector>
#include <cassert>

#define BUFFER_DAFAULT_SIZE 1024
class Buffer
{
private:
    std::vector<char> _buffer;  //使用vector进行内存空间管理
    uint64_t _read_idx;         //读偏移
    uint64_t _write_idx;        //写偏移
public:
    Buffer():_read_idx(0),_write_idx(0),_buffer(BUFFER_DAFAULT_SIZE)
    {}
    char *Begin(){return &*_buffer.begin();} //获取起始元素的迭代器再对迭代器进行解引用,再取地址(&)就是第0个元素的空间地址
    
    //1.获取当前写入位置的地址,//buffer空间起始地址 + 写偏移量
    char *WritePosition(){return Begin() + _write_idx;}
    //2.获取当前读取数据位置的地址
    char *ReadPosition(){return Begin() + _read_idx;}
	//3.获取缓冲区末尾空闲空间大小---写偏移之后的空闲空间  总体空间大小 - 写偏移
    uint64_t TailIdleSize(){return _buffer.size() - _write_idx;}
	//4.获取缓冲区起始空闲空间大小---读偏移之前的空闲空间大小
    uint64_t HeadIdleSize(){return _read_idx;}
    //5.获取可读数据大小
    uint64_t ReadAbleSize(){return _write_idx - _read_idx;}
    //6.将读偏移向后移动
    void MoveReadOffset(uint64_t len)
    {
        //向后移动大小必须小于可读数据大小
        assert(len <= ReadAbleSize());
        _read_idx += len;}
    //7.将写偏移向后移动
    void MoveWriteOffset(uint64_t len)
    {
        //向后移动大小必须小于当前后边空间的大小
        assert(len <= TailIdleSize());
        _write_idx += len;
    }
    //8.确保可写位置足够(足够移动,不够扩容)
    void EnsureWriteSpace(uint64_t len)
    {
        //如果末尾空闲空间大小足够,直接返回
        if(len <= TailIdleSize())
        {
            return;
        }
        //如果末尾空间不够,判断加上起始位置空闲空间大小看够不够,够了将数据移动到起始位置
        if(len <= TailIdleSize() + HeadIdleSize())
        {
            //将数据移动到起始位置
            uint64_t rsz = ReadAbleSize();//将当前可读数据大小先保存起来
            std::copy(ReadPosition(),ReadPosition() + rsz,Begin());//把可读数据拷贝到起始位置
            _read_idx = 0; //将读位置置0
            _write_idx = rsz; //将写位置置为可读数据大小,因为当前的可读数据大小就是当前的写偏移
        }
        else
        {
            //总体空间不够,则需要扩容
            //不移动数据,直接给写偏移之后扩容足够空间
            _buffer.resize(_write_idx + len);
        }
    }
    //9.写数据
    void Write(const void* data,uint64_t len)
    {
        //1.保证有足够空间 2.拷贝数据进去
        const char *d = (const char*)data;
        EnsureWriteSpace(len);
        std::copy(d,d+len,WritePosition());
    }
    void WriteAndPush(const void *data,uint64_t len)
    {
        Write(data,len);
        MoveWriteOffset(len);
    }
    //写入string类型的数据
    void WriteString(const std::string &data)
    {
        return Write(data.c_str(),data.size());
    }
    void WriteStringAndPush(const std::string &data)
    {
        Write(data.c_str(),data.size());
        MoveWriteOffset(data.size());
    }
    //写入Buffer类型数据
    void WriteBuffer(Buffer &data)
    {
        return Write(data.ReadPosition(),data.ReadAbleSize());
    }
    void WriteBufferAndPush(Buffer &data)
    {
        WriteBuffer(data);
        MoveWriteOffset(data.ReadAbleSize());
    }
    //10.读数据
    void Read(void* buf,uint64_t len)
    {
        //获取数据大小必须小于可读数据大小
        assert(len <= ReadAbleSize());
        std::copy(ReadPosition(),ReadPosition() + len,(char*)buf);
    }
    //将数据读取并弹出
    void ReadAndPop(void *buf,uint64_t len)
    {
        Read(buf,len);
        MoveReadOffset(len);
    }
    //以string的方式读取数据
    std::string ReadAsString(uint64_t len)
    {
        assert(len <= ReadAbleSize());
        std::string str;
        str.resize(len);
        Read(&str[0],len);
        return str;
    }
    //以string的方式读取数据并弹出
    std::string ReadAsStringAndPop(uint64_t len)
    {
        std::string str = ReadAsString(len);
        MoveReadOffset(len);
        return str;
    }
    //查找换行符
    char *FindCRLF()
    {
        void *res = memchr(ReadPosition(),'\n',ReadAbleSize());
        return (char*)res;
    }
    //通常获取一行数据,这种情况针对的是asscii码值
    std::string GetLine()
    {
        char *pos = FindCRLF();
        if(pos == nullptr)
        {
            return "";
        }
        //加1是为了把换行字符也提取出来
        return ReadAsString(pos - ReadPosition() + 1);
    }
    std::string GetLineAndPop()
    {
        std::string str =  GetLine();
        MoveReadOffset(str.size());
        return str;
    }
	//11.清空缓冲区
    void Clean()
    {
        //只需要将偏移量归0即可
        _read_idx = 0;
        _write_idx = 0;
    }
};

日志打印宏的编写

cpp 复制代码
#define INF 0
#define DBG 1
#define ERR 2
#define LOG_LEVEL INF
#define LOG(level,format,...)do{\
    if(level < LOG_LEVEL) break;\
    time_t t = time(nullptr);\
    struct tm *ltm = localtime(&t);\
    char tmp[32] = {0};\
    strftime(tmp,31,"%H:%M:%S",ltm);\
    fprintf(stdout,"[%s %s:%d]" format "\n",tmp,__FILE__,__LINE__,##__VA_ARGS__);\
}while(0)
#define INF_LOG(format,...) LOG(INF,format,##__VA_ARGS__)
#define DBG_LOG(format,...) LOG(DBG,format,##__VA_ARGS__)
#define ERR_LOG(format,...) LOG(ERR,format,##__VA_ARGS__)

上面的代码做了三层封装:

定义日志等级

cpp 复制代码
#define INF 0
#define DBG 1
#define ERR 2
#define LOG_LEVEL INF

含义:

  • INF:普通信息
  • DBG:调试信息
  • ERR:错误信息
  • LOG_LEVEL:当前日志输出阈值

核心日志宏

cpp 复制代码
#define LOG(level,format,...)do{\
	if(level < LOG_LEVEL) break; \
	...打印日志... \
}while(0)

核心作用:根据等级决定打不打印日志

二次封装

cpp 复制代码
#define INF_LOG(format,...)
LOG(INF,format,##__VA_ARGS__)
#define DBG_LOG(format,...)
LOG(DBG,format,##__VA_ARGS__)
#define ERR_LOG(format,...)
LOG(ERR,format,##__VA_ARGS__)

总结:

  • 用宏实现类似printf的日志接口
  • 用__FILE__和__LINE__定位代码位置
  • 用LOG_LEVEL控制日志的输出等级
  • 用二次宏(INF_LOG /DBG_LOG)简化调用
  • 用 ##__VA_ARGS__解决空参数问题
  • 用do while(0)保证宏安全

Socket模块:套接字模块

Socket套接字类功能设计

Socket.hpp

cpp 复制代码
class Socket
{
private:
    int _sockfd;
public:
    Socket():_sockfd(-1){}
    //通过描述符构建socket对象
    Socket(int fd):_sockfd(fd){}
    ~Socket(){Close();}
    int Fd(){return _sockfd;}
    //1.创建套接字
    bool Creat()
    {
        //int socket(int domain,int type,int protocol)
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(sockfd < 0)
        {
            ERR_LOG("CREAT SOCKET FAILED!!!");
            return false;
        }
        _sockfd = sockfd;
        return true;
    }
    //2.绑定地址信息
    bool Bind(const std::string &_ip,uint64_t port)
    {
        //组织一个IPV4的地址结构
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr(_ip.c_str());
        socklen_t len = sizeof(struct sockaddr_in);

        //int bind(int sockfd,struct sockaddr* addr,socklen_t len)
        int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
        if(ret < 0)
        {
            ERR_LOG("BIND ADDRESS FAILED");
            return false;
        }
        return true;
    }
    //3.开始监听
    bool Listen(int backlog = MAX_LISTEN)
    {
        int ret = listen(_sockfd,backlog);
        if(ret < 0)
        {
            ERR_LOG("SOCKET LISTEN FAILED!");
            return false;
        }
        return true;
    }
    //4.向服务器发起连接
    bool Connect(const std::string &ip,uint64_t port)
    {
        //int connect(int sockfd,struct sockaddr* addr,socklen_t len);
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr(ip.c_str());
        socklen_t len = sizeof(struct sockaddr_in);

        int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
        if(ret < 0)
        {
            ERR_LOG("CONNECT SERVER FAILED!");
            return false;
        }
        return true;
    }
    //5.获取新连接
    int Accept()
    {
        //int accept(int sockfd,struct sockaddr* addr,socklen_t *len);
        int newfd = accept(_sockfd,nullptr,nullptr);
        if(newfd < 0)
        {
            // 处理非阻塞模式下的EAGAIN错误
            if(errno == EAGAIN || errno == EINTR) 
            { 
                return -2; // 特殊返回值,表示需要重试
            } 
            ERR_LOG("SOCKET ACCEPT FAILED!");
            return -1;
        }
        return newfd;
    }
    //6.接收数据 flag为标志位,表示非阻塞
    ssize_t Recv(void* buf,size_t len,int flag = 0)
    {
        //ssize_t recv(int sockfd,void *buf,size_t len.int flage)
        ssize_t ret = recv(_sockfd,buf,len,flag);
        if(ret <= 0)
        {
            //EAGAIN表示当前socket的接收缓冲区没有数据了,只要在非阻塞的情况下才有这个错误
            //EINTR表示当前socket的阻塞等待,被信号打断了
            if(errno == EAGAIN || errno == EINTR)
            {
                return 0;//表示这次接收没有接收到数据
            }
            ERR_LOG("SOCKET RECV FAILED!");
            return -1;
        }
        return ret;//实际接收到的数据长度
    }

    //非阻塞接收
    ssize_t NonBlockRecv(void *buf,size_t len)
    {
        return Recv(buf,len,MSG_DONTWAIT); //MSG_DONTWAIT是一个非阻塞标志,表示当前接收是一个非阻塞
    }
    //7.发送数据
    ssize_t Send(const void *buf,size_t len,int flag = 0)
    {
        //ssize_t send(int sockfd,void*data,size_t len,int flag)
        ssize_t ret = send(_sockfd,buf,len,flag);
        if(ret < 0)
        {
            // 处理非阻塞模式下的EAGAIN错误
            if(errno == EAGAIN || errno == EINTR) 
            { 
                return 0; // 表示这次发送没有发送数据
            } 
            ERR_LOG("SOCKET SEND FAILED!");
            return -1;
        }
        return ret;//实际发送的数据长度
    }
    //非阻塞发送
    ssize_t NonBlockSend(void *buf,size_t len)
    {
        if(len == 0) return 0;
        return Send(buf,len,MSG_DONTWAIT);//MSG_DONTWAIT表示非阻塞发送
    }
    //8.关闭套接字
    void Close()
    {
        if(_sockfd != -1)
        {
            close(_sockfd);
            _sockfd = -1;
        }
    }
    //9.创建一个服务端连接
    bool CreatServer(uint64_t port,const std::string &ip = "0.0.0.0",bool block_flag = false)
    {
        //1.创建套接字
        if(Creat() == false) return false;
        if(block_flag) NonBlock();
        //2.绑定地址
        if(Bind(ip,port) == false) return false;
        //3.开始监听
        if(Listen() == false) return false;
        //4.设置非阻塞
        //NonBlock();
        //5.启动地址复用
        ReuseAddress();

        return true;
    }
    //10.创建一个客户端连接
    bool CreateClient(uint64_t port,const std::string &ip)
    {
        //1.创建套接字
        if(Creat() == false) return false;
        //2.连接服务器
        if(Connect(ip,port) == false) return false;

        return true;
    }
    //11.创建套接字选项,开启地址端口复用
    void ReuseAddress()
    {
        //int setsockopt(int fd, int level,int optname,int void* value,int vallen)
        int val = 1;
        setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR,(void*)&val,sizeof(int));
        val = 1;
        setsockopt(_sockfd,SOL_SOCKET,SO_REUSEPORT,(void*)&val,sizeof(int));
    }
    //12.设置套接字阻塞属性 --- 设置为非阻塞
    void NonBlock()
    {
        //int fcntl(int fd,int cmd, .../* arg */);
        int flag = fcntl(_sockfd,F_GETFL,0);
        fcntl(_sockfd,F_SETFL,flag | O_NONBLOCK);
    }
};

Channel模块:事件管理模块

Channel类的设计

目的: 对描述符的监控事件管理

功能:

(1)事件管理:

① 描述符是否可读

② 描述符是否可写

③ 对描述符监控可读

④ 对描述符监控可写

⑤ 解除可读事件监控

⑥ 解除可写事件监控

⑦ 解除所有事件监控

(2)事件触发后的处理管理

① 需要处理的事件:可读事件、可写事件、挂断事件、错误事件、任意事件

② 事件处理的回调函数

Channel类的接口定义:

cpp 复制代码
class Channel
{
private:
    uint32_t _events;   //当前需要监控的事件
    uint32_t _revent;   //当前连接触发的事件
    using EventCallback = std::function<void()>;
    EventCallback _read_callback;   //可读事件触发后的回调
    EventCallback _write_callback;  //可写事件触发后的回调
    EventCallback _err_callback;    //错误事件触发后的回调
    EventCallback _close_callback;  //连接断开事件被触发后的回调
    EventCallback _event_callback;  //任意事件被触发后的回调

public:
    Channel(){}

    void SetReadCallback(const EventCallback &cb){}
    void SetWrtieCallback(const EventCallback &cb){}
    void SetErrorCallback(const EventCallback &cb){}
    void SetCloseCallback(const EventCallback &cb){}
    void SetEventCallback(const EventCallback &cb){}

    bool ReadAble(){}       //当前是否可读
    bool WriteAble(){}      //当前是否可写
    void EnableRead(){}     //启动读事件监控
    void EnableWrite(){}    //启动写事件监控
    void DisableRead(){}    //关闭读事件监控
    void DisableWrtie(){}   //关闭写事件监控
    void DisableAll(){}     //关闭所有事件监控
    void Remove(){}         //移除监控
    void HandleEvent(){}    //事件处理,一旦连接触发了事件,就调用这个函数,自己触发了什么事件,如何处理自己决定
};

Channel类的接口实现:

cpp 复制代码
class Channel
{
private:
    int _fd;     
    EventLoop *_loop;
    uint32_t _events;   //当前需要监控的事件
    uint32_t _revents;   //当前连接触发的事件
    using EventCallback = std::function<void()>;
    EventCallback _read_callback;   //可读事件触发后的回调
    EventCallback _write_callback;  //可写事件触发后的回调
    EventCallback _error_callback;    //错误事件触发后的回调
    EventCallback _close_callback;  //连接断开事件被触发后的回调
    EventCallback _event_callback;  //任意事件被触发后的回调

public:
    Channel(EventLoop *loop,int fd):_fd(fd),_events(0),_revents(0),_loop(loop){}
    int Fd(){return _fd;}
    uint32_t Events(){return _events;}      //获取想要监控的事件
    void SetRevents(uint32_t events){_revents = events;}   //获取实际就绪的事件
    void SetReadCallback(const EventCallback &cb){_read_callback = cb;}
    void SetWriteCallback(const EventCallback &cb){_write_callback = cb;}
    void SetErrorCallback(const EventCallback &cb){_error_callback = cb;}
    void SetCloseCallback(const EventCallback &cb){_close_callback = cb;}
    void SetEventCallback(const EventCallback &cb){_event_callback = cb;}

    //是否监控了可读
    bool ReadAble(){ return _events & EPOLLIN;}    
    //是否监控了可写
    bool WriteAble(){ return _events & EPOLLOUT;}     
    //启动读事件监控
    void EnableRead(){ _events |= EPOLLIN;Update();}  
    //启动写事件监控   
    void EnableWrite(){ _events |= EPOLLOUT;Update();}    
    //关闭读事件监控
    void DisableRead(){ _events &= ~EPOLLIN;Update();}    
    //关闭写事件监控
    void DisableWrite(){_events &= ~EPOLLOUT;Update();}   
    //关闭所有事件监控
    void DisableAll(){_events = 0;Update();}   
     //移除监控  
    void Remove();  
    void Update();  
    //事件处理,一旦连接触发了事件,就调用这个函数,自己触发了什么事件,如何处理自己决定
    void HandleEvent()
    {
        if((_revents & EPOLLIN) || (_revents & EPOLLRDHUP) /*连接断开自动触发可读事件*/ || (_revents & EPOLLPRI)/*优先带外数据*/)
        {
            if(_read_callback) _read_callback();
        }
        if(_revents & EPOLLOUT)
        {
            if(_write_callback) _write_callback();
        } //有可能会释放连接的操作,一次只处理一个
        else if(_revents & EPOLLERR)
        {
            if(_error_callback) _error_callback(); //一旦出错就会释放连接,没必要调用任意回调了,因此要放到前面调用任意事件回调
        }
        else if(_revents & EPOLLHUP)
        {
            if(_close_callback) _close_callback();
        }
        if(_event_callback) _event_callback();  //放到事件处理完毕后调用,刷新活跃度
    }    
};

Poller模块:描述符IO事件监控模块

意义: 通过epoll实现对描述符的IO事件监控

功能:

(1)添加 / 修改描述符的事件监控 (不存在则添加,存在则更新)

(2)移除描述符的事件监控

封装思想:

(1)必须拥有一个epoll的操作句柄

(2)拥有一个 struct epoll_event结构数组,监控时保存所有的活跃事件

(3)使用一个哈希表,管理描述符和描述符对应的事件管理Channel对象

逻辑流程:

①对描述符进行监控,通过Channel才能知道描述符需要监控什么事件

②当描述符就绪了,通过描述符在哈希表中给找到对应的Channel(得到了Channel才能知道什么事件该如何处理),当描述符就绪了返回就绪描述符对应的Channel

cpp 复制代码
class Poller
{
private:
	int _epfd;
	struct epoll_event _evs[xxx];
	std::unordered_map<int,Channel*> 
private:
	1.判断要更新事件的描述符是否存在
	2.针对epoll直接操作(添加,修改,移除)
public:
	1.添加或更新描述符所监控的事件
	2.移除描述符的监控
	3.开始监控,获取就绪的Channel
};

Poller模块类接口实现:

cpp 复制代码
#define MAX_EPOLLEVENTS 1024
class Poller
{
private:
    int _epfd;
    struct epoll_event _evs[MAX_EPOLLEVENTS];
    std::unordered_map<int,Channel *> _channels;
private:
    //对epoll的直接操作
    void Update(Channel* channel,int op)
    {
        //int epoll_ctll(int epfd,int op,int fd,struct_event *event)
        int fd = channel->Fd();
        struct epoll_event ev;
        ev.data.fd = fd;
        ev.events = channel->Events();
        int ret = epoll_ctl(_epfd,op,fd,&ev);
        if(ret < 0)
        {
            ERR_LOG("EPOLLCTL FAILED!");
        }
        return;
    }
    //判断一个Channel是否添加了事件监控
    bool HasChannel(Channel* channel)
    {
        auto it = _channels.find(channel->Fd());
        if(it == _channels.end())
        {
            return false;
        }
        return true;
    }
public:
    Poller()
    {
        _epfd = epoll_create(MAX_EPOLLEVENTS);
        if(_epfd < 0)
        {
            ERR_LOG("EPOLLCREAT FAILED!");
            abort(); //退出程序
        }
    }
    //添加或者修改事件监控
    void UpdateEvent(Channel* channel)
    {
        bool ret = HasChannel(channel);
        if(ret == false)
        {
            //不存在则添加
            _channels.insert(std::make_pair(channel->Fd(),channel));
            return Update(channel,EPOLL_CTL_ADD);
        }
        //存在则修改
        return Update(channel,EPOLL_CTL_MOD);
    }
    //移除监控
    void RemoveEvent(Channel* channel)
    {
        auto it = _channels.find(channel->Fd());
        if(it != _channels.end())
        {
            _channels.erase(it);
        }
        Update(channel,EPOLL_CTL_DEL);
    }
    //开始监控,返回活跃连接
    void Poll(std::vector<Channel*> *active)
    {
        //int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
        int nfds = epoll_wait(_epfd ,_evs,MAX_EPOLLEVENTS,-1);//-1表示阻塞式监控 nfds表示就绪的事件个数
        if(nfds < 0)
        {
            if(errno == EINTR) //表示阻塞被信号打断了
            {
                return;
            }
            ERR_LOG("EPOLL WAIT ERROR:%s\n",strerror(errno));
            abort();//退出程序
        }
        for(int i = 0; i < nfds; i++)
        {
            auto it = _channels.find(_evs[i].data.fd);
            assert(it != _channels.end());
            it->second->SetRevents(_evs[i].events); //设置实际就绪事件
            active->push_back(it->second);
        }
        return;
    }
};

void Channel::Remove(){ return _poller->RemoveEvent(this);}    
void Channel::Update(){ return _poller->UpdateEvent(this);}    

eventfd
介绍
eventfd:创建一个描述符用于事件的通知

区分信号。信号是对进程进行事件通知的

eventfd本质在内核里面管理的就是一个计数器,创建evnetfd就会在内核中创建一个计数器,每当向eventfd中写入一个数值---用于表示事件通知次数,可以使用read进行数据读取,读取到的数据就是通知的次数。读取之后,计数清零。

用处: 在EventLoop模块实现事件通知功能。

cpp 复制代码
#include <sys/eventfd.h>
int eventfd(unsigned int intval,int flags);
功能:创建一个eventfd对象,实现事件的通知
参数:
	intval:计数初值
	flags:
		EFD_ELOEXEC - 禁止进程复制
		EFD_NONBLOCK - 启动非阻塞属性
	返回值:返回一个文件描述符用于操作
	eventfd也是通过read/write/close进行操作的
	注意点:read&write进行IO的时候,数据只能是一个8字节数据

优点:

(1)相比于pipe更轻量:

  • 开销更低:pipe需要维护两个文件描述符和一个内核缓冲区,而eventfd只需要一个文件描述符和一个简单的64位整数计数器。
  • 资源占用少:每一个eventfd只消耗一个文件描述符,这在需要大量通知通道场景下优势明显。

(2)原子性操作:eventfd的读写操作都是原子性的,这意味着多个线程可以并发的读写,而不需要额外的锁保护。


EventLoop模块:事件监控以及处理的模块

关键点:这个模块与线程是一一对应关联的。

监控了一个连接,而这个连接一旦就绪,就要进行事件处理。如果这个描述符在多个事件中都触发了事件,进行处理,就会存在线程安全问题。因此我们需要将一个连接的事件监控,以及连接事件处理,以及其他操作,都放在同一个线程中进行。

如何保证对一个连接的所有操作都在EventLoop线程中?

解决方案:给EventLoop中添加一个队列,对连接的所有操作,都进行一次封装,将对连接的操作并不直接执行,而是当作任务添加到任务的队列中。这样就能保证对连接的所有操作,都是在一个线程中执行的,不涉及线程安全问题。

但是对任务队列的操作有线程安全问题。只需要给task的操作加一把锁。

EventLoop处理流程:

①在线程中,对描述符进行事件监控

②有描述符就绪,则对描述符进行事件处理

③所有的就绪事件处理完了,这时候再将任务队列中的任务一一执行。

功能:

(1)事件监控

①使用poller模块

②有事件就绪则进行事件处理

(2)执行任务队列中的任务

①一个线程的任务队列

注意点:因为有可能因为等待描述符IO事件就绪,导致执行流程阻塞,这时候任务队列中的任务将得不到执行。

因此得有一个任务通知的东西,能够唤醒事件通知的阻塞。

当时间就绪,需要处理的时候,在处理的过程中,如果要对连接进行某些操作,这些操作都应该保证在EventLoop对应的线程中执行,保证对连接的各项操作都是线程安全的。

  1. 如果要执行的操作本就在线程中,就不需要将操作压入队列了,可以直接执行
  2. 如果要执行的操作不在线程中,才需要加入任务池,等到事件处理完了,然后执行任务。

EventLoop模块代码实现:

cpp 复制代码
class EventLoop
{
private:
    using  Functor = std::function<void()>;
    std::thread::id _thread_id;  //线程ID
    int _event_fd;               //event_fd唤醒事件监控有可能导致的阻塞
    std::unique_ptr<Channel> _event_channel; //当EventLoop释放的时候,unique_ptr也会释放
    Poller _poller;              //进行所有描述符的事件监控
    std::vector<Functor> _tasks;  //任务池
    std::mutex _mutex;            //任务池操作的线程安全
    TimerWheel _timer_wheel;      //定时器模块
public:
    //执行任务池中的所有任务
    void RunAllTask()
    {
        std::vector<Functor> functor;
        {
            //加锁
            std::unique_lock<std::mutex> _lock(_mutex);//出了作用域就会被销毁,自动解锁
            _tasks.swap(functor);
        }
        for(auto &f : functor)
        {
            f();
        }      
        return;  
    }
    static int CreateEventFd()
    {
        int efd = eventfd(0,EFD_CLOEXEC | EFD_NONBLOCK);
        if(efd < 0)
        {
            ERR_LOG("CTREATE EVENTFD FAILED !");
            abort();//让程序异常退出
        }
        return efd;
    }
    void ReadEventFd()
    {
        uint64_t res = 0;
        int ret = read(_event_fd,&res,sizeof(res));
        if(ret < 0)
        {
            //EINTR - 表示被信号打断   EAGAIN - 表示无数据可读
            if(errno == EINTR || errno == EAGAIN) 
            {
                return;
            }
            ERR_LOG("READ EVENTFD FAILED!");
            abort();//异常退出
        }
        return;
    }
    void WeakUpEventFd()
    {
        uint64_t val = 1;
        int ret = write(_event_fd,&val,sizeof(val));
        if(ret < 0)
        {
            if(errno == EINTR)
            {
                return;
            }
            ERR_LOG("WRITE EVENTFD FAILED!");
            abort();//异常退出
        }
        return;
    }
public:
    EventLoop()
    :_thread_id(std::this_thread::get_id())
    ,_event_fd(CreateEventFd())
    ,_event_channel(new Channel(this,_event_fd))
    ,_timer_wheel(this)
    {
        //给eventfd添加可读事件回调函数,读取eventfd事件通知次数
        _event_channel->SetReadCallback(std::bind(&EventLoop::ReadEventFd,this));
        //启动eventfd读事件监控
        _event_channel->EnableRead();
    }
    //启动EventLoop模块   三步走:事件监控 -> 就绪事件处理 -> 执行任务
    void Start()
    {
        while(1)
        {
            //1.事件监控   
            std::vector<Channel*> actives; //活跃连接
            _poller.Poll(&actives);
            //2.事件处理
            for(auto &channel :actives)
            {
                channel->HandleEvent();//进行事件回调
            }
            //3.任务执行
            RunAllTask();//执行任务池中的任务
        }
    }
    //用于判断当前线程是否是EventLoop对应的线程                        
    bool IsInLoop()
    {
        return (_thread_id/*当前EventLoop的线程id*/ == std::this_thread::get_id()); /*获取当前运行线程的id*/
    }
    void AssertInLoop()
    {
        assert(_thread_id == std::this_thread::get_id());
    }
    //判断当前执行的任务是否在当前线程中,如果在就执行,如果不在,就加入到任务队列中
    void RunInLoop(const Functor &cb)
    {
        if(IsInLoop())
        {
            return cb();
        }
        return  QueueInLoop(cb);
    }   
    //将操作压入任务池
    void QueueInLoop(const Functor &cb)
    {
        {
            std::unique_lock<std::mutex> _lock(_mutex);
            _tasks.push_back(cb);
        }
        //唤醒有可能因为没有事件就绪而导致的epoll阻塞
        //其实就是给eventfd写入一个数据,eventfd就会触发可读事件
        WeakUpEventFd();
    }              
    //添加或修改描述符的事件监控
    void UpdateEvent(Channel* channel){ return _poller.UpdateEvent(channel);}  
    //移除描述符的事件监控
    void RemoveEvent(Channel* channel){ return _poller.RemoveEvent(channel);}     
    //添加定时任务             
    void TimerAdd(uint64_t id,uint32_t delay,const TaskFunc &cb){return _timer_wheel.TimerAdd(id,delay,cb);}
    //刷新定时任务
    void TimerRefresh(uint64_t id){return _timer_wheel.TimerRefresh(id);}
    //取消定时任务
    void TimerCancel(uint64_t id){return _timer_wheel.TimerCancel(id);}    
    //是否有某个定时器任务的村子
    bool HasTimer(uint64_t id){return _timer_wheel.HasTimer(id);}
};

TimerWheel定时器模块

定时器模块的整合:
timefd:实现内核每隔一段时间,给进程一次超时事件(timefd可读事件)。
timerwheel:实现每次执行Runtimetask,都可以执行一波到期的定时任务。

要实现一个完整的秒级定时器,就要将这两个功能整合到一起。

timefd设置为每秒钟触发一次定时事件,当事件被触发,则运行一次timerwheeld的Runtimetask,执行一下所有的过期定时任务。

而timerfd的事件监控与触发,可以融合EventLoop来实现。

cpp 复制代码
using TaskFunc = std::function<void()>;  //定时器要执行的任务函数
using ReleaseFunc = std::function<void()>;
//定时器任务对象
class TimerTask
{
private:
    uint64_t _id;            //定时器任务对象id
    uint32_t _timeout;       //定时任务的超时时间
    bool _canceled;          //定时器任务是否取消 false-表示不取消
    TaskFunc _task_cb;       //定时器要执行的定时任务
    ReleaseFunc _release;    //用于删除TimerWheel中保存的定时器对象信息,删除_timers
public:
    TimerTask(uint64_t id,uint32_t delay,const TaskFunc &cb)
    :_id(id)
    ,_timeout(delay)
    ,_canceled(false)
    ,_task_cb(cb)
    {}

    ~TimerTask()
    {
        //执行任务
        if(_canceled == false) _task_cb();
        _release();
    }
    void Cancel(){ _canceled = true;}
    void SetRelease(const ReleaseFunc &cb) { _release = cb;}
    uint32_t DelayTime(){return _timeout;}
    
    
};
//时间轮
class TimerWheel
{
private:
    using WeakTask = std::weak_ptr<TimerTask>;
    using PtrTask = std::shared_ptr<TimerTask>;
    int _tick;          //当前的秒针走到哪里释放哪里,释放哪里就相当于执行任务
    int _capacity;      //表盘的最大数量----其实就是最大的延迟时间
    std::vector<std::vector<PtrTask>> _wheel; //存shared_ptr(决定生死)
    std::unordered_map<uint64_t,WeakTask> _timers; //存weak_ptr(只是索引)

    EventLoop *_loop;
    int _timerfd;//定时器描述符  -- 可读事件回调就是读取计数器,执行定时任务
    std::unique_ptr<Channel> _timer_channel;
private:
    void RemoveTimer(uint64_t id)
    {
        auto it = _timers.find(id);
        if(it != _timers.end())
        {
            _timers.erase(it);
        }
    }
    static int CreateTimerFd()
    {
        int timerfd = timerfd_create(CLOCK_MONOTONIC,0);
        if(timerfd < 0)
        {
            ERR_LOG("TIMERFD CREATE FAILED!");
            abort();
        }
        //int timefd_settime(int fd,int flags,struct itimerspec *new,struct itimerspec *old);
        struct itimerspec itime;
        itime.it_value.tv_sec = 1;
        itime.it_value.tv_nsec = 0;         //第一次的超时时间为1秒后
        itime.it_interval.tv_sec = 1;
        itime.it_interval.tv_nsec = 0;      //第一次超时后,每次的间隔为1秒
        timerfd_settime(timerfd,0,&itime,nullptr);
        return timerfd;
    }
    int ReadTimerFd()
    {
        uint64_t times;
        //有可能因为其他描述符的事件处理事件比较长,然后在处理定时器描述符事件的时候,有可能就超时了很多次
        //read读到的数据times就是从上一次read之后超时的次数
        int ret = read(_timerfd,&times,8);
        if(ret < 0)
        {
            ERR_LOG("READ TIMERFD FAILED!");
            abort();
        }
        return times;
    }
    //这个函数应该每秒被执行一次,相当于秒针向后走了一步
    void RunTimeTask()
    {
        _tick = (_tick + 1) % _capacity;
        _wheel[_tick].clear();//清空指定位置数组,就会把数组中保存的所有管理定时器对象的shared_ptr释放掉
    }
    void OnTime()
    {
        //根据实际的超时次数,执行对应的超时任务
        int times = ReadTimerFd();
        for(int i = 0;i < times; i++)
        {
            RunTimeTask();
        }
    }
    //添加定时任务
    void TimerAddInLoop(uint64_t id,uint32_t delay,const TaskFunc &cb)
    {
        PtrTask pt(new TimerTask(id,delay,cb)); //创建任务对象
        pt->SetRelease(std::bind(&TimerWheel::RemoveTimer,this,id)); //设置"销毁时清理索引"
        int pos = (_tick + delay) % _capacity;
        _wheel[pos].push_back(pt);   //强引用
        _timers[id] = WeakTask(pt);  //弱引用
    }
    //修改定时任务(刷新,延时定时任务)
    void TimerRefreshInLoop(uint64_t id)
    {
        //通过保存的定时器对象的weak_ptr构造一个shard_ptr出来,添加到轮子中
        auto it = _timers.find(id);
        if(it == _timers.end())
        {
            return;//没找到定时任务,没法刷新,没法延迟
        }
        PtrTask pt = it->second.lock(); ///lock获取weak_ptr管理的对象对应的shared_ptr,把weak_ptr转变为shared_ptr
        if(!pt) return;
        int delay = pt->DelayTime();
        int pos = (_tick + delay) % _capacity;
        _wheel[pos].push_back(pt);
    }
    //取消定时任务
     void TimerCancelInLoop(uint32_t id)
    {
        auto it = _timers.find(id);
        if(it == _timers.end())
        {
            return;
        }
        PtrTask pt = it->second.lock();
        if(pt) pt->Cancel();
    }
public:
    TimerWheel(EventLoop *loop)
    :_capacity(60)
    ,_tick(0)
    ,_wheel(_capacity)
    ,_loop(loop)
    ,_timerfd(CreateTimerFd())
    ,_timer_channel(new Channel(_loop,_timerfd))
    {
        _timer_channel->SetReadCallback(std::bind(&TimerWheel::OnTime,this));
        _timer_channel->EnableRead(); //启动读事件监控
    }
    /*定时器中有个_timers成员,定时器信息的操作有可能在多个线程中进行,因此需要考虑线程安全问题*/
    /*不想加锁,那就把对定时器的所有操作都放在一个线程中进行*/
    void TimerAdd(uint64_t id,uint32_t delay,const TaskFunc &cb);
    //修改定时任务(刷新,延时定时任务)
    void TimerRefresh(uint64_t id);
    //取消定时任务
    void TimerCancel(uint32_t id);
    /*这个接口存在线程安全问题 -- 这个接口实际上不能被外界使用者调用,只能在模块内,在对应的EventLoop线程内执行*/
    bool HasTimer(uint64_t id)
    {
        auto it = _timers.find(id);
        if(it == _timers.end())
        {
            return false;
        }
        return true;
    }
    ~TimerWheel()
    {}
};

遇到的问题:

定义变量的时候 ,把_wheel定义在了_capacity的前面了,导致了后面初始化_wheel(_capacity)的时候_capacity还没有没初始化。导致的报错。

用gdb进行调试


Connection模块:对通信连接进行整体管理的模块

Connection模块是对于通信连接进行整体管理的模块,对通信连接的所有操作都是通过这么模块的功能实现的。

管理:

(1)套接字的管理,能够进行套接字的操作。

(2)连接事件的管理,可读,可写,错误,关闭,任意。

(3)缓冲区的管理,便于Socket数据的接收和发送。

(4)协议上下文的管理,记录请求数据的处理过程。

(5)回调函数的管理

  • 因为连接接收到数据之后如何处理需要由用户决定,因此必须有业务处理回调函数。
  • 一个连接建立成功之后,该如何处理,要由用户决定,因此要必须有连接建立成功的回调函数
  • 一个连接建立关闭前,该如何处理,由用户决定,因此要有关闭连接的回调函数。
    任意事件的产生,有没有某些处理,由用户决定,因此也要由任意事件的回调函数

功能:

(1)发送数据 --- 给用户提供的数据发送接口,并不是真正的发送数据接口,而是把数据放到发送缓冲区,启动写事件监控。

(2)关闭连接 --- 提供给用户,并不是真正的关闭接口,因该在实际释放连接之前,看一看输入输出缓冲区有没有数据要处理。

(3)启动非活跃连接的超时销毁功能。

(4)取消给非跃连接的超时销毁功能。

(5)协议切换,一个连接,接收数据之后,如何进行业务处理,取决于上下文,以及数据的业务处理回调函数。

Connection模块是连接的管理模块,对连接的所有操作都是通过这个模块完成的。

场景:对连接操作的时候,但是连接已经释放,导致内存访问错误,最终服务器崩溃。

解决方案:使用智能指针shared_ptr,对Connection对象来进行管理,只要任意一个使用Connection的地方,都保存了一份shared_ptr,其他地方就算是释放了shared_ptr也没关系,因为计数不为0,不会导致Connetion的实际释放。

代码实现:

cpp 复制代码
class Connection;
//DISCONECTED-连接关闭状态 CONNECTING-连接建立成功-待处理状态  
//CONNECTED-连接建立完成,各种设置已设置,可以通信的状态
//DISCONNECTING-待关闭状态
typedef enum{ DISCONNECTED,CONNECTING,CONNECTED,DISCONNECTING}Connstatu;
using PtrConnection = std::shared_ptr<Connection>;
class Connection : public std::enable_shared_from_this<Connection>
{
private:
    uint64_t _conn_id;      //连接的唯一id,便于连接的管理和查找
    //uint64_t _time_id;      //定时器ID,必须是唯一的,这一块为了简化操作,我们将_conn_id,作为我们的定时器id
    int _sockfd;            //连接关联的文件描述符
    bool _enable_inactive_release;  //连接是否启动非活跃连接销毁的判断标志,默认为false
    EventLoop *_loop;       //连接所关联的EventLoop
    Connstatu _statu;       
    Socket _socket;         //套接字操作管理
    Channel _channel;       //连接的事件管理
    Buffer _in_buffer;      //输入缓冲区 - 存放从socket中读取的数据
    Buffer _out_buffer;     //输出缓冲区 - 存放要发送给对端的数据
    Any _context;           //请求的接收处理上下文

    /*这四个回调函数是让服务器模块设置的(其实服务器的处理回调也是组建的使用者来设置的)*/
    /*换句话说,这几个回调都是我们的组件使用者使用的*/
    //连接成功的回调
    using ConnectedCallback = std::function<void(const PtrConnection&)>;
    //业务处理的回调
    using MessageCallback = std::function<void(const PtrConnection&,Buffer *)>; //对缓冲区的数据进行业务处理
    //关闭阶段的处理回调
    using  ClosedCallback = std::function<void(const PtrConnection&)>;
    //任意事件触发的回调
    using AnyEventCallback = std::function<void(const PtrConnection&)>;
    ConnectedCallback _connected_callback;
    MessageCallback _message_callback;
    ClosedCallback _closed_callback;
    AnyEventCallback _event_callback;
    /*组件内的连接关闭回调 -- 组件内设置的,服务器组件内会把所有的连接管理起来,一旦某个连接要关闭,就应该从管理的地方移除自己的信息*/
    ClosedCallback _server_closed_callback;
private:
    /*五个Channel的事件回调函数*/
    //描述符触发可读事件后回调的函数,接收socket数据放到接收缓冲区中,然后调用_message_callback进行业务处理
    void HandleRead()  
    {
        //1.接收Socket的数据,放到缓冲区
        char buf[65536];
        ssize_t ret = _socket.NonBlockRecv(buf,65535);//非阻塞接收
        if(ret < 0)
        {
            //出错了,不能直接关闭
            return ShutdownInLoop();
        }
        //rett == 0,表示没有读到数据,并不是连接断开了,连接断开返回的是-1
        //将数据放入输入缓冲区,写入之后,将写偏移向后移动
        _in_buffer.WriteAndPush(buf,ret);
        //2.调用message_callback进行业务处理
        if(_in_buffer.ReadAbleSize() > 0)
        {
            //shared_from_this() - 从当前对象自身获取自身的shared_ptr管理对象
            return _message_callback(shared_from_this(),&_in_buffer);
        }
    }
    //描述符触发可写事件后的回调函数,将发送缓冲区中的数据进行发送        
    void HandleWrite()
    {
        //_out_buffer中保存的数据,就是要发送的数据
        ssize_t ret = _socket.NonBlockSend(_out_buffer.ReadPosition(),_out_buffer.ReadAbleSize());  
        if(ret < 0)
        {
            //发送错误就应该关闭连接了,但这时候需要检查一下接收缓冲区中有没有数据要处理
            if(_in_buffer.ReadAbleSize() > 0)
            {
                _message_callback(shared_from_this(),&_in_buffer);
            }
            return Release();//实际的关闭释放操作
        }
        _out_buffer.MoveReadOffset(ret);
        if(_out_buffer.ReadAbleSize() == 0)
        {
            _channel.DisableWrite(); //没有数据发送了,关闭写事件监控·
            //如果当前是连接待关闭状态,有数据则发送发送完数据关闭连接,没有数据则直接释放
            if(_statu == DISCONNECTING)
            {
                return Release();
            }
        }
        return;
    }        
    //描述符触发挂断事件的回调函数
    void HandleClose()
    {
        /*一旦连接挂断了,套接字什么都干不了,因此有数据待处理就处理一下,完毕关闭连接*/
        if(_in_buffer.ReadAbleSize() > 0)
        {
            _message_callback(shared_from_this(),&_in_buffer);
        }
        return Release();
    }        
    //描述符触发出错事件
    void HandleError()
    {
        return HandleClose();
    }       
    //描述符触发任意事件  1.刷新连接的活跃度-延迟定时销毁任务 2.调用组件使用者的任意事件回调
    void HandleEvent()
    {
        if(_enable_inactive_release == true){ _loop->TimerRefresh(_conn_id);}
        if(_event_callback) { _event_callback(shared_from_this());}
    }       
    //连接获取之后所处的状态下,要进行的各个设置(给Channel设置事件回调,启动读监控)
    void EstablishedInLoop()
    {
        //1.修改连接状态; 2.启动读事件监控; 3.调用回调函数
        assert(_statu == CONNECTING); //当前状态必须是上层的半连接状态
        _statu = CONNECTED;           //这条语句执行完毕,则连接就进入到了已连接状态
        _channel.EnableRead();
        if(_connected_callback) _connected_callback(shared_from_this());
    }  
    //这个接口才是实际的释放接口
    void ReleaseInLoop()
    {
        // 1.修改连接状态,将其设置为DISCONNECTED 
        _statu = DISCONNECTED ;  
        // 2.移除事件监控 
        _channel.Remove();
        // 3.关闭描述符 
        _socket.Close();
        // 4.如果当前任务队列中还有定时销毁任务,则取消任务
        if(_loop->HasTimer(_conn_id)) CancelInactiveReleaseInLoop();
        // 5.调用关闭回调函数 ,避免先移除服务器管理的连接信息,导致Connection被释放,再去处理会报错,因此先调用用户的回调函数
        if(_closed_callback) _closed_callback(shared_from_this());
        //移除服务器内部管理的连接信息
        if(_server_closed_callback) _server_closed_callback(shared_from_this());
    }
    //这个接口并不是实际的发送数据接口,而是把数据放到了发送缓冲区 ,启动了可写事件监控    
    void SendInLoop(Buffer &buf)
    {
        if(_statu == DISCONNECTED) return; //如果连接是关闭状态,什么事都不做
        _out_buffer.WriteBufferAndPush(buf);
        if(_channel.WriteAble() == false)
        {
            _channel.EnableWrite();
        }
    }
    //这个关闭操作并非实际的连接释放操作,需要判断有没有数据待处理,待发送
    void ShutdownInLoop()
    {
        _statu = DISCONNECTING;//设置半关闭状态
        if(_in_buffer.ReadAbleSize() > 0)
        {
            if(_message_callback)
            {
                _message_callback(shared_from_this(),&_in_buffer);
            }
        }
        //要么是写入数据的时候出错关闭,要么是没有待发送数据直接关闭
        if(_out_buffer.ReadAbleSize() > 0)
        {
            if(_channel.WriteAble() == false)
            {
                _channel.EnableWrite();
            }

        }
        if(_out_buffer.ReadAbleSize() == 0)
        {
            Release(); 
        }
    }
    //启动非活跃连接超时释放规则
    void EnableInactiveReleaseInLoop(int sec)
    {
        //1.将判断标志 _enable_inactive_release 设置为 true
        _enable_inactive_release = true;
        //2.如果定时销毁任务存在,则刷新一下定时时间即可
        if(_loop->HasTimer(_conn_id)){ return _loop->TimerRefresh(_conn_id);}
        //3.如果不存在定时销毁任务,则新增
        _loop->TimerAdd(_conn_id,sec,std::bind(&Connection::ReleaseInLoop,this));
    }
    void CancelInactiveReleaseInLoop()
    {
        _enable_inactive_release = false;
        if(_loop->HasTimer(_conn_id))
        {
            _loop->TimerCancel(_conn_id);
        }
    }
    //切换协议
    void UpgradeInLoop(const Any &context
        ,const ConnectedCallback &conn
        ,const MessageCallback &msg
        ,const ClosedCallback &closed
        ,const AnyEventCallback &event)
    {
        _context = context; //设置上下文
        _connected_callback = conn;
        _message_callback = msg;
        _closed_callback = closed;
        _event_callback = event;
    }
public:
    Connection(EventLoop *loop,uint64_t conn_id,int sockfd)
    :_conn_id(conn_id)
    ,_sockfd(sockfd)
    ,_enable_inactive_release(false)
    ,_loop(loop)
    ,_statu(CONNECTING)
    ,_socket(_sockfd)
    ,_channel(loop,sockfd)
    {
        _channel.SetCloseCallback(std::bind(&Connection::HandleClose,this));
        _channel.SetEventCallback(std::bind(&Connection::HandleEvent,this));
        _channel.SetReadCallback(std::bind(&Connection::HandleRead,this));
        _channel.SetWriteCallback(std::bind(&Connection::HandleWrite,this));
        _channel.SetErrorCallback(std::bind(&Connection::HandleError,this));
    }
    ~Connection(){DBG_LOG("RELEASE CONNECTION:%p",this);}
    //获取管理的文件描述符
    int Fd() {return _sockfd; }
    //获取连接ID
    int Id() {return _conn_id;}
    //返回状态
    bool Connected(){return (_statu == CONNECTED);} //是否处于Connected状态
    //设置上下文 - 连接建立完成时
    void SetContext(const Any &context){_context = context;}
    //获取行下文 - 返回的是一个指针
    Any *GetContext(){ return &_context;}
    void SetConnectedCallback(const ConnectedCallback &cb){ _connected_callback = cb;}
    void SetMessageCallback(const MessageCallback &cb){_message_callback = cb;}
    void SetClosedCallback(const ClosedCallback &cb){_closed_callback = cb;}
    void SetAnyEventCallback(const AnyEventCallback &cb){_event_callback = cb;}
    void SetSrvClosedCallback(const ClosedCallback &cb){_server_closed_callback = cb;}
    //连接建立就绪后,进行Channel进行回调设置,启动读监控,调用_connected_callback
    void Established()
    {
        _loop->RunInLoop(std::bind(&Connection::EstablishedInLoop,this));
    } 
    //发送数据,将数据放到发送缓冲区,启动写事件监控
    void Send(const char* data,size_t len)
    {
        //外界传入的 data 可能是一个临时的空间,我们现在只是把发送操作压如任务池,有可能没有立即被执行
        //因此,有可能执行的时候,data指向的空间,有可能已经被释放了
        Buffer buf;
        buf.WriteAndPush(data,len);
        _loop->RunInLoop(std::bind(&Connection::SendInLoop,this,std::move(buf)));
    }
    //提供给组件使用者的关闭接口 - 并不实际关闭,需要判断有没有数据待处理
    void Shutdown()
    {
        _loop->RunInLoop(std::bind(&Connection::ShutdownInLoop,this));
    }
    void Release() 
    {
        _loop->QueueInLoop(std::bind(&Connection::ReleaseInLoop, this));
    }
    //启动非活跃连接销毁 sec表示启动多长时间无通信算非活跃连接,添加定时任务
    void EnableInactiveRelease(int sec)
    {
        _loop->RunInLoop(std::bind(&Connection::EnableInactiveReleaseInLoop,this,sec));
    }
    //取消非活跃连接销毁
    void CancelInactiveRelease()
    {
        _loop->RunInLoop(std::bind(&Connection::CancelInactiveReleaseInLoop,this));
    }
    //协议切换 - 重置上下文,以及阶段性处理函数 - 这个进口必须在EventLoop线程中立即执行,防备新的事件触发后,处理的时候,切换任务还没有被执行,导致数据用原协议处理
    void Upgrade(const Any &context,const ConnectedCallback &conn,const MessageCallback &msg,
        const ClosedCallback &closed,const AnyEventCallback &event)
    {
        _loop->AssertInLoop();
        _loop->RunInLoop(std::bind(&Connection::UpgradeInLoop,this,context,conn,msg,closed,event));
    }
};

Acceptor模块:对监听套接字进行管理

(1)创建一个监听套接字

(2)启动读事件监控

(3)事件触发后,获取新连接

(4)调用新连接获取成功后的函数

而这个函数的功能是:

为新连接创建Connection进行管理(这一步不是accept模块操作的,而是服务器模块 )

因为Accept模块只进行监听连接的管理,因此获取到新连接的描述符之后,对于新连接的描述符如何处理它并不关心。

对于新连接如何处理,应该是服务器模块来管理的。

服务器模块,实现了对新连接描述符的处理函数,将这个函数设置给Accept模块中的回调函数

cpp 复制代码
class Acceptor
{
private:
    Socket _socket;     //监听套接字
    EventLoop *_loop;   //用于对监听套接字进行事件监控
    Channel _channel;   //用于对监听套接字进行事件管理

    using AcceptCallback = std::function<void(int)>;
    AcceptCallback _accept_callback;
private:
    //监听套接字的读事件处理回调函数 - 获取新连接,调用AcceptCallback函数,进行新连接处理
    void HandleRead()
    {
        int newfd = _socket.Accept();
        if(newfd < 0)
        {
            return;
        }
        if(_accept_callback) _accept_callback(newfd);
    }
    int CreateServer(int port)
    {
        bool ret = _socket.CreatServer(port);
        assert(ret == true);
        return _socket.Fd();
    }
public:
    /*不能将启动读事件监控,放到构造函数中,必须在设置回调函数后,再去启动*/
    /*否则有可能造成启动监控后,立即有事件,处理的时候,回调函数还没有设置,新连接得不到处理,且资源泄漏*/
    Acceptor(EventLoop *loop,int port)
    :_socket(CreateServer(port))
    ,_loop(loop)
    ,_channel(loop,_socket.Fd())
    {
       _channel.SetReadCallback(std::bind(&Acceptor::HandleRead,this));
    }


    //对回调函数的设置
    void SetAcceptCallback(const AcceptCallback &cb){ _accept_callback = cb;}
    void Listen(){_channel.EnableRead();}
};


    //对回调函数的设置
    void SetAcceptCallback(const AcceptCallback &cb){ _accept_callback = cb;}
    void Listen(){_channel.EnableRead();}
};

LoopThread模块

目标: 将EventLoop模块与线程整和起来

EventLoop模块与线程是一一对应的。

EventLoop模块实例化的对象,在构造的时候就会初始化_thread_id,而后边运行的时候判断当前是否运行在EventLoop模块对应的线程中,就是将线程Id与EventLoop模块中的thread_id进行比较,相同就表示在同一个线程里,不同就表示当前运行的线程并不是EventLoop线程。

含义: EventLoop模块在实例化对象的时候,必须在线程内部

EventLoop在实例化对象的时候会设置自己的thread_id

如果我们先创建多个EventLoop对象,然后创建了多个线程,将各个线程的id,重新给EventLoop进行设置。

存在问题:在构造EventLoop对象,到设置新的thread_id期间将是不可控的。

因此我们必须先创建线程,然后在线程的入口函数中,去实例化EventLoop对象

构造一个新的模块:LoopThread

这个模块的功能:将EventLoop与thread整合到一起

思想:

1.创建线程

2.在线程中实例化EventLoop对象
功能: 可以向外部返回实例化的EventLoop

LoopThread模块的代码实现:

cpp 复制代码
class LoopThread
{
private:
    /*用于实现_loop的同步关系,避免线程创建了,在 _loop还没有实例化之前去获取_loop*/
    std::mutex _mutex;      //互斥锁
    std::condition_variable _cond;//条件变量
    EventLoop *_loop;       //EventLoop指针变量,这个对象需要在线程内实例化
    std::thread _thread;    //EventLoop对应的线程
private:
    /*实例化EvenLoop对象,唤醒_cond上有可能阻塞的线程,并且运行EventLoop模块对应的功能*/
    void ThreadEntey()
    {
        EventLoop loop;
        {
            std::unique_lock<std::mutex> lock(_mutex);  //加锁
            _loop = &loop;
            _cond.notify_all();
        }
        _loop->Start();
    }
public:
    /*创建线程,设定线程入口函数*/
    LoopThread()
    :_loop(nullptr)
    ,_thread(std::thread(&LoopThread::ThreadEntey,this)){}
    /*返回当前线程关联的EventLoop对象指针*/
    EventLoop *GetLoop()
    {
        EventLoop *loop = nullptr;
        {
            std::unique_lock<std::mutex> lock(_mutex);          //加锁
            _cond.wait(lock,[&](){return _loop != nullptr;});   //loop为空,就一直阻塞,等待被唤醒
            loop = _loop;
        }
        return loop;
    }
};

LoopThreadPoll模块

针对LoopThread设计一个线程池,便与使用者对线程的控制。

LoopThreadPoll模块:对所有的LoopThread进行管理和分配

功能:

  1. 线程数量可配置(0个或多个)

注意事项:在服务器中,主从Reactor模型,主线程只负责新连接的获取,从属Reactor线程进行新连接的事件监控以及事件的处理,因此当前的线程池,从属线程的数量有可能为0,也就是实现单Reactor服务器。一个线程及负责获取连接,也负责连接的处理。

  1. 对所有的线程进行管理,其实就是管理0个或多个LoopThread对象
  2. 提供线程分配的功能

当主线程获取了一个新连接,需要将新连接挂到从属线程上进行事件监控及处理。

假设有0个从属线程,则直接分配给主线程的EventLoop进行处理

假设有多个从属线程,则采用RR轮转(轮转分配)思想,进行线程的分配(将对应线程的EventLoop获取到,设置给对应的Connection)\

LoopThreadPoll模块代码实现:

cpp 复制代码
class LoopThreadPool
{
private:
    int _thread_count;                  //从属线程的数量
    int _next_idx;                 //RR轮转
    EventLoop* _baseloop;               //主EventLoop.从属线程数量为0,则所有的操作都在_baseloop中进行
    std::vector<LoopThread*> _threads;  //保存所有的LoopThread对象
    std::vector<EventLoop*> _loops;     //从属线程的数量如果大于0,在从_loop中进行EventLoop线程分配
public:
    LoopThreadPool(EventLoop *baseloop)
    :_thread_count(0)
    ,_next_idx(0)
    ,_baseloop(baseloop)                                                   
    {}
    //设置线程数量
    void SetThreadCount(int count){_thread_count = count;}
    //创建所有的从属线程
    void Create()
    {
        if(_thread_count > 0)
        {
            _threads.resize(_thread_count);
            _loops.resize(_thread_count);
            for(int i = 0;i < _thread_count; i++)
            {
                _threads[i] = new  LoopThread();
                _loops[i] = _threads[i]->GetLoop(); 
            }
        }
    }
    EventLoop *NextLoop()
    {
        if(_thread_count == 0)
        {
            return _baseloop;
        }
        _next_idx = (_next_idx + 1) % _thread_count;
        return _loops[_next_idx];
    }
};

TcpServer模块

TcpServer模块:对所有模块的整合,通过TcpServer实例化的对象,可以非常简单的完成一个服务器的搭建。

管理:

  1. Acceptor对象,创建一个监听套接字
  2. EventLoop对象:这个对象是一个baseloop对象,实现对监听套接字的事件监控
  3. std::unordered_mapr<uint64_t,PtrConnection> conns对象:实现对所有新建连接的管理
  4. LoopThreadPool对象:创建一个loop线程池,对新建连接进行事件监控及处理

功能:

  1. 设置从属线程池数量
  2. 启动服务器
  3. 设置各种回调函数(连接建立完成回调函数,消息回调函数,关闭回调函数,任意回调函数),用户设置给TcpServer,TcpServer设置给获取的新连接
  4. 是否启动非活跃连接超时销毁功能
  5. 添加定时任务功能

流程:

  1. 在TcpServer中实例化出来一个Acceptor对象 ,以及一个EventLoop对象(baseloop)
  2. 将Acceptor挂在baseloop上进行事件监控
  3. 一旦Acceptor对象就绪可读事件,则执行读事件回调函数获取新连接
  4. 对新连接创建Connection进行管理
  5. 对连接对应的Connection设置对应的功能回调(连接完成,消息,关闭,任意事件回调)
  6. 启动Connection非活跃连接超时销毁规则
  7. 将新连接对应的Connection挂到LoopThreadPool从属线程中对应的EventLoop中进行事件监控
  8. 一旦Connection对应的连接就绪了可读事件,则这时候执行读事件回调函数,读取数据,读取完毕后调用TcpServer设置的消息回调函数,进行业务处理

TcpServer模块的代码实现:

cpp 复制代码
class TcpServer
{
private:
    uint64_t _next_id;      //这是一个自动增长的连接id
    int _timeout;           //这是非活跃连接的统计时间-多长时间没有通信就属于非活跃连接
    int _port;
    bool _enable_inactive_release;//是否启动了非活跃连接超时销毁的判断标志
    EventLoop _baseloop;    //这是主线程的EventLoop对象,负责监听事件的处理
    Acceptor _acceptor;     //这是监听套接字的管理对象
    LoopThreadPool _pool;   //这是从属的EventLoop线程池
    std::unordered_map<uint64_t,PtrConnection> _conns; //保存所有连接对应的shared_ptr对象

    using ConnectedCallback = std::function<void(const PtrConnection&)>;
    //业务处理的回调
    using MessageCallback = std::function<void(const PtrConnection&,Buffer *)>; //对缓冲区的数据进行业务处理
    //关闭阶段的处理回调
    using ClosedCallback = std::function<void(const PtrConnection&)>;
    //任意事件触发的回调
    using AnyEventCallback = std::function<void(const PtrConnection&)>;
    using Functor = std::function<void()>;
    ConnectedCallback _connected_callback;
    MessageCallback _message_callback;
    ClosedCallback _closed_callback;
    AnyEventCallback _event_callback;
private:
    void RunAfterInLoop(const Functor &task,int delay)
    {
        _next_id++;
        _baseloop.TimerAdd(_next_id,delay,task);
    }
    //为新连接构造Connection进行管理
    void NewConnectionn(int fd)
    {
        _next_id++;
        PtrConnection conn(new Connection(_pool.NextLoop(),_next_id,fd));
        conn->SetMessageCallback(_message_callback); //-1就是"占位符",表示这个参数不是现在给,而是等以后调用时再传进来
        conn->SetClosedCallback(_closed_callback);
        conn->SetConnectedCallback(_connected_callback);
        conn->SetAnyEventCallback(_event_callback);
        conn->SetSrvClosedCallback(std::bind(&TcpServer::RemoveConnection,this,std::placeholders::_1));
        if(_enable_inactive_release) conn->EnableInactiveRelease(_timeout); //启动非活跃超时销毁
        conn->Established();//就绪初始化
        _conns.insert(std::make_pair(_next_id,conn));
    }
    void RemoveConnectionInLoop(const PtrConnection &conn)
    {
        int id = conn->Id();
        auto it = _conns.find(id);
        if(it != _conns.end())
        {
            _conns.erase(id);
        }
    }
    //从管理Connection的_conns中移除Connection的管理信息
    void RemoveConnection(const PtrConnection &conn)
    {
        _baseloop.RunInLoop(std::bind(&TcpServer::RemoveConnectionInLoop,this,conn));
    }
public:
    TcpServer(int port)
    :_port(port)
    ,_next_id(0)
    ,_enable_inactive_release(false)
    ,_acceptor(&_baseloop,port)
    ,_pool(&_baseloop)
    {
        _acceptor.SetAcceptCallback(std::bind(&TcpServer::NewConnectionn,this,std::placeholders::_1));
        _acceptor.Listen();//将监听套接字挂到baseLoop上
    }
    void SetThreadCount(int count){ return _pool.SetThreadCount(count);}

    void SetConnectedCallback(const ConnectedCallback &cb){ _connected_callback = cb;}
    void SetMessageCallback(const MessageCallback &cb){_message_callback = cb;}
    void SetClosedCallback(const ClosedCallback &cb){_closed_callback = cb;}
    void SetAnyEventCallback(const AnyEventCallback &cb){_event_callback = cb;}
    void EnableInactiveRelease(int timeout){_timeout = timeout;_enable_inactive_release = true;}
    //用于添加一个定时任务
    void RunAfter(const Functor &task,int delay)                                            
    {
        _baseloop.RunInLoop(std::bind(&TcpServer::RunAfterInLoop,this,task,delay));
    }
    void Start()
    {
        _pool.Create();//创线程中的从属线程
        _baseloop.Start();
    }
};

void Channel::Remove(){ return _loop->RemoveEvent(this);}    
void Channel::Update(){ return _loop->UpdateEvent(this);}    
/*不想加锁,那就把对定时器的所有操作都放在一个线程中进行*/
void TimerWheel::TimerAdd(uint64_t id,uint32_t delay,const TaskFunc &cb)
{
    _loop->RunInLoop(std::bind(&TimerWheel::TimerAddInLoop,this,id,delay,cb));
}
//修改定时任务(刷新,延时定时任务)
void TimerWheel::TimerRefresh(uint64_t id)
{
    _loop->RunInLoop(std::bind(&TimerWheel::TimerRefreshInLoop,this,id));
}  
//取消定时任务
void TimerWheel::TimerCancel(uint32_t id)
{
    _loop->RunInLoop(std::bind(&TimerWheel::TimerCancelInLoop,this,id));
}

class NetWork
{
public:
    NetWork()
    {
        DBG_LOG("SIGPIPE INIT");
        signal(SIGPIPE,SIG_IGN); //IGPIPE 防止连接关闭的时候,继续发送数据导致send触发异常,导致程序退出
    }
};
相关推荐
用户03284722207012 小时前
如何搭建本地yum源(上)
运维
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质3 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz4 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工4 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智4 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_4 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
古城小栈4 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
施努卡机器视觉4 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造