仿muduo库实现高并发服务器-上

目录

buffer缓冲区类

日志打印宏

Socket封装套接字类

Channel模块

Poller模块

EventLoop模块

定时器知识点:

这里先讲一下shared_ptr与push_back

一个问题+weak_ptr的讲解

接着设计时间轮

和TimerWheel时间轮的整合的模块

EventLoop和TimerWheel的整合

Any类:能够接收各种不同类型的数据

Connection模块

Acceptor模块,只进行监听连接


buffer缓冲区类

设计思路

#include <iostream>
#include <vector>
#include <string>
#include <cassert>
#include <cstring>

#define BUFFER_DEFAULT_SIZE 1024
class Buffer
{
private:
    std::vector<char> _buffer; // 缓冲区
    uint64_t _writer_idx;      // 写偏移
    uint64_t _reader_idx;      // 读偏移
public:
    Buffer() : _reader_idx(0), _writer_idx(0), _buffer(BUFFER_DEFAULT_SIZE)
    {
    }
    char *Begin()
    {
        // 指针+整数 = 指针偏移,char* + uint64_t= 偏移 = 数字 × sizeof (char)
        return &(*_buffer.begin());
    }
    // 获取当前写入起始地址
    char *WritePosition()
    {
        // _buffer的空间起始地址+写偏移量
        return Begin() + _writer_idx;
    }

    // 获取当前读取起始位置
    char *ReadPosition()
    {
        return Begin() + _reader_idx;
    }

    // 获取当前空闲空间大小-+write后
    uint64_t TailIdleSize()
    {
        return _buffer.size() - _writer_idx;
    }

    // 获取当前空闲空间大小-read前
    uint64_t HeadIdleSize()
    {
        return _reader_idx;
    }

    // 获取可读数据大小
    uint64_t ReadAbleSize()
    {
        return _writer_idx - _reader_idx;
    }

    // 读偏移向后移动
    void MoveReadOffset(uint64_t len)
    {
        // 向后移动长度,必须小于 可读数据大小
        assert(len <= ReadAbleSize());
        _reader_idx += len;
    }

    // 写偏移向后移动
    void MoveWriteOffset(uint64_t len)
    {
        // 向后移动长度,必须小于 可写数据大小-前+后之和
        assert(len <= TailIdleSize());
        _writer_idx += len;
    }

    // 确保可写空间足够-整体前移/扩容,需要确保有len大的空闲空间,不够就扩容
    void EnsureWriteSpace(uint64_t len)
    {
        if (TailIdleSize() >= len) // 说明能写下
            return;
        // 末尾空闲大小不够,则判断+起始位置空闲空间大小是否足够
        // 够,整体移动数据
        if (len <= HeadIdleSize() + TailIdleSize())
        {
            uint64_t rsz = ReadAbleSize(); // 当前数据大小先保存
            // 数据移动std::copy(起始迭代器, 结束迭代器(不包含), 目标起始位置);
            std::copy(ReadPosition(), ReadPosition() + rsz, Begin());

            _reader_idx = 0;
            _writer_idx = rsz;
        }
        else // 总体空间不够,扩容,不移动数据到前面,因为这样的话数据要移动两次,麻烦,直接给写偏移之后扩容足够空间
        {
            _buffer.resize(_writer_idx + len); // 最多需要的总体空间大小
        }
    }

    // 写入数据
    void Write(const void *data, uint64_t len)
    {
        // 1.保证有足够空间
        EnsureWriteSpace(len);
        // 2.拷贝数据进去
        // 注意data是void*,没有步长,+len不知道加多少字节
        const char *d = (const char *)data;
        std::copy(d, d + len, WritePosition());
    }

    void WriteAndPush(const void *data, uint64_t len)
    {
        Write(data, len);
        MoveWriteOffset(len);
    }

    void WriteString(const std::string &data) // 把string内容写道缓冲区里
    {
        Write(data.c_str(), data.size());
    }

    void WriteStringAndPush(const std::string &data)
    {
        WriteString(data);
        MoveWriteOffset(data.size());
    }

    void WriteBuffer(Buffer &data)
    {
        Write(data.ReadPosition(), data.ReadAbleSize());
    }
    void WriteBufferAndPush(Buffer &data)
    {
        WriteBuffer(data);
        MoveWriteOffset(data.ReadAbleSize());
    }

    // 读取数据
    void Read(void *buf, uint64_t len)
    {
        // 1.确保读取数据不超过 可读数据大小
        assert(len <= ReadAbleSize());
        // 2.把数据读取,拷贝到buff
        std::copy(ReadPosition(), ReadPosition() + len, (char *)buf);
    }
    void ReadAndPop(void *buf, uint64_t len)
    {
        Read(buf, len);
        MoveReadOffset(len);
    }
    std::string ReadAsString(uint64_t len)
    {
        assert(len <= ReadAbleSize());
        std::string str;
        str.resize(len);
        Read(&str[0], len); // 不用str.c_str(),返回值const char*,不允许修改对应地址内容
        return str;
    }

    std::string ReadAsStringAndPop(uint64_t len)
    {
        assert(len <= ReadAbleSize());

        std::string str;
        str = ReadAsString(len);
        MoveReadOffset(len);
        return str;
    }
    // 获取一行数据
    char *FindCRLF()
    {
        char *res = (char *)memchr(ReadPosition(), '\n', ReadAbleSize());
        return res;
    }
    // 返回一行数据
    std::string GetLine()
    {
        char *pos = FindCRLF();
        if (pos == nullptr)
        {
            return "";
        }
        return ReadAsString(pos - ReadPosition() + 1); // 把\n也包含进去
    }

    std::string GetLineAndPop()
    {
        std::string str = GetLine();
        MoveReadOffset(str.size());
        return str;
    }

    // 清空缓冲区
    void Clear()
    {
        // 偏移量归0
        _writer_idx = 0;
        _reader_idx = 0;
    }
};

日志打印宏

打印等级的日志信息,普通信息打印,错误信息打印,调试信息打印,调试完不需要删除打印代码,只打印出错信息

std::string line="hello!";

//正常打印

std::cout<<line<<std::endl;

//宏定义

#define LOG(msg) fprintf(stdout,"%s",msg)

LOG(line.c_str());

//添加行号:C++有3个好用的宏,__FILE__,当前文件名;__LINE,当前行号;__FUNCTION__,当前函数名

#define LOG(msg) fprintf(stdout, "[%s:%d] %s", __FILE__, __LINE__, msg)

但是当前这个格式固定是%s,只能打印字符串char*,要支持任意格式%d,%f,%s

#define LOG(format, msg) fprintf(stdout, "[%s:%d] " format, __FILE__, __LINE__, msg)

但是当前参数固定,只是一个msg参数,改成多个,用...代替msg,宏__VA_ARGS__代替msg

把我写的内容,以某种格式,添加到fprintf的format里了

#define LOG(format, ...) fprintf(stdout, "[%s:%d] " format, __FILE__, __LINE__, __VA_ARGS__)

直接调用 LOG("%s",line.c_str());

但它规定死了,必须传2个参数,第一个是format,第2个是msg数据,不能LOG("hello")

这样,相当于只传入一个一个参数,报错!

加2个##,就是##__VA_ARGS__,没有参数也能处理

#define LOG(format, ...) fprintf(stdout, "[%s:%d] " format, __FILE__, __LINE__, ##__VA_ARGS__)

#define LOG(format, ...) fprintf(stdout, "[%s:%d] " format "\n", __FILE__, __LINE__, ##__VA_ARGS__)

LOG("我是日志");

变成:"[%s:%d] " + "我是日志" + "\n"

LOG("我是内容:%s", "测试");

fprintf(stdout, "[%s:%d] 我是内容:%s\n", __FILE__, __LINE__, "测试");

再加上时间

加上日志等级

Socket封装套接字类

套接字是两台主机收发数据的桥梁,它负责让A主机发数据,B主机收数据

核心功能:

1.建立连接,让客户端和服务器都能找到对方

2.发送数据,把数据从本主机发送给另一台主机

3.接收数据,另一台主机从网络上把对方发来的数据接收

4.关闭连接,通信结束,断开通道

    //创建一个服务端连接 不需要知道客户端的ip+port,因为客户端主动连服务器,已经提供了客户端自己的ip+port,后续服务器直接给这个客户端的ip+port发消息就行

    //这里传入的参数是 服务器的ip+port,因为服务器socket要bind绑定自己所在的ip+port,因为它要固定端口,这样客户端才能找到服务器!

    //但是我们又不需要传入服务器的ip,因为服务器可以有多个ip,我要监听、绑定所有本机ip-    0.0.0.0,保证所有发到这台主机8080 端口的连接,都能收到! 因为客户端可能 向服务器    127.0.0.1,8080 主动建立连接,可能向服务器 192.168.0.1,8080 主动建立连接,它们都属于同一台服务器的ip,但是如果指定本主机的某个ip,可能会导致,发给本主机 的部分建立连接的请求,监听不到!

    bool CreateServer(uint16_t port,const std::string& ip="0.0.0.0")

    {

        //1.创建套接字

        int fd=socket(AF_INET,SOCK_STREAM,0);

        //绑定ip+port

        bind(fd,&addr,sizeof(addr));

        //3.监听,等待客户端连接

        listen(fd);

    }

    //创建一个客户端连接 需要给服务器的ip+port,因为客户端需要 connect连接服务器

    //客户端不需要手动bind,OS会自动给客户端分配ip!自动绑定!

    bool CreateClient(const std::string& ip,uint16_t port)

    {

        //1.创建套接字

        int fd=socket(AF_INET,SOCK_STREAM,0);

        //2.连接服务器

        connect(fd,&server_addr,sizeof(server_addr));

    }

#define MAX_LISTEN 1024
class Socket
{
public:
    Socket():_socketfd(-1)
    {}
    Socket(int fd):_socketfd(fd)
    {}
    ~Socket()
    {
        Close();
    }
    int Fd() {return _socketfd;}
    //1.建立连接
    //创建套接字
    bool Create()
    {
        _socketfd=socket(AF_INET,SOCK_STREAM,0);
        if(_socketfd<0)
        {
            ERR_LOG("create socket failed");
            return false;
        }
        return true;
    }
    //socket绑定ip+port
    bool Bind(const std::string& ip,uint16_t port)
    {
        struct sockaddr_in addr;

        addr.sin_family=AF_INET;
        addr.sin_port=htons(port);//转网络字节序
        addr.sin_addr.s_addr=inet_addr(ip.c_str());//点分十进制的ip转成网络字节序的32为整数
        socklen_t len=sizeof(addr);
        int ret=bind(_socketfd,(struct sockaddr*)&addr,len);
        if(ret<0)
        {
            ERR_LOG("bind address failed");
            return false;
        }
        return true;
    }
    //客户端主动连接服务端
    bool Connect(const std::string& ip,uint16_t port)
    {
        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(addr);
        int ret=connect(_socketfd,(struct sockaddr*)&addr,len);
        if(ret<0) 
        {
            ERR_LOG("connect failed");
            return false;
        }
        return true;
    }
    
    //服务端监听
    bool Listen(int backog=MAX_LISTEN)
    {
        int ret=listen(_socketfd,backog);
        if(ret<0)
        {
            ERR_LOG("listen failed");
            return false;
        }
        return true;
    }
    //服务端获取客户端的新连接
    int Accept()
    {
        int flags = fcntl(_socketfd, F_GETFL);
        std::cout << "当前 socket 非阻塞标志:" << (flags & O_NONBLOCK) << std::endl;

        struct sockaddr_in addr;
        socklen_t len=sizeof(addr);
        int newfd=accept(_socketfd,nullptr,nullptr);
        if(newfd<0)
        {
            ERR_LOG("accept client failed");
            return -1;
        }
        return newfd;
    }
    //2.接收数据
    ssize_t Recv(void* buf,size_t len,int flag=0)
    {
        ssize_t ret=recv(_socketfd,buf,len,flag);
        if(ret<=0)
        {
            //<0,出错了;=0无数据可读
            if(errno==EAGAIN || errno==EINTR)
            {
                return 0;//非阻塞状态下无数据可读/被信号中断
            }
            ERR_LOG("socket recv error");
            return -1;
        }
        return ret;
    }
    //非阻塞接收数据
    ssize_t NonBlockRecv(void* buf,size_t len)
    {
        return Recv(buf,len,MSG_DONTWAIT);//没有数据就报错返回
    }

    //3.发送数据
    ssize_t Send(const void* buf,size_t len,int flag=0)
    {
        ssize_t ret=send(_socketfd,buf,len,flag);//实际发送的数据长度
        if(ret<0)
        {
            ERR_LOG("socket send failed");
            return -1;
        }
        return ret;
    }
    //非阻塞发送数据
    ssize_t NoNBlockSend(void* buf,size_t len)
    {
        return Send(buf,len,MSG_DONTWAIT);//发送缓冲区满了就报错返回
    }

    //4.关闭套接字
    void Close()
    {
        if(_socketfd!=-1)
        {
            close(_socketfd);
            _socketfd=-1;
        }
    }

    //综合:创建一个服务端连接,服务器不主动连接client,recv接收客户端消息时候,就能拿到clientip+port,
    //然后给cient发数据就行,但此时为什么还要传递ip+port?因为服务器socket需要绑定 服务器端的ip+port呀!
    // 服务端:我在本机所有IP的 8888 端口等
    bool CreateServer(uint16_t port,const std::string& ip="0.0.0.0",bool block_flag=false)
    {
        //1.创建套接字
        if(Create()==false) return false;
        //2.设置套接字为非阻塞
        if(block_flag==true)
            NonBlock();

        //3.绑定ip+port
        if(Bind(ip,port)==false) return false;
        //4.开始监听
        if(Listen()==false) return false;
        
        //5.启动port重用
        ReauseAddress();
        return true;
    }
    //创建一个客户端连接 ip+port知道要连那个服务端
    bool CreateClient(uint16_t port,const std::string& ip)
    {
        //1.创建套接字
        if(Create()==false) return false;
        //2.建立连接
        if(Connect(ip,port)==false) return false;
        return true;
    }

    //其他功能,设置socket的端口复用
    void ReauseAddress()
    {
        int opt=1;
        setsockopt(_socketfd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));
        opt=1;
        setsockopt(_socketfd,SOL_SOCKET,SO_REUSEPORT,(void*)&opt,sizeof(opt));
    }
    //将套接字设置为非阻塞,send\recv就不用单独设置flag
    void NonBlock()
    {
        int flag=fcntl(_socketfd,F_GETFL);//获取属性
        fcntl(_socketfd,F_SETFL,flag|O_NONBLOCK);//不是覆盖
    }

private:
    int _socketfd;
};

Channel模块

功能:封装fd,把一个文件描述符fd+它关心的事件+事件发生时要调用的回调函数,打包在一起的“事件管理器”

class Channel
{
private:
    int _fd;
    //这个fd需要监控的事件
    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(int fd):_fd(fd),_events(0),_revents(0)
    {}
    ~Channel()
    {}
    int Fd()
    {
        return _fd;
    }
    void SetREvents(uint32_t revents)
    {
        _revents=revents;
    }
    //描述符是否是可读的
    bool ReadAble()
    {
        return _revents & EPOLLIN;
    }
    //是否可写
    bool WriteAble()
    {
        return _revents & EPOLLOUT;
    }
    //启动可读,启动对读事件监控
    void EnableRead()
    {
        _events|=EPOLLIN;
        return;
    }
    //启动可写
    void EnableWrite()
    {
        _events|=EPOLLOUT;
        return;
    }
    //解除可读,还挂在红黑树上,只是不再触发读事件
    void DisableRead()
    {
        _events&=(~EPOLLIN);
        return;
    }
    //解除可写,解除对写事件的监控
    void DisableWrite()
    {
        _events&=(~EPOLLOUT);
        return;
    }
    //解除所有事件的监控,只是不再触发任何事件,还挂在红黑树上
    void DisableAll()
    {
        _events=0;
        //EpollLoop
        return;
    }
    //移除事件监控,把事件从红黑树上拿下来
    void Remove()
    {
        //EventLoop
    }
    //设置回调函数
    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;
    }
    //一旦连接触发了事件,就调用这个函数
   void HandleEvent()
    {
        if((_revents&EPOLLIN)||(_revents&EPOLLRDHUP)||(_revents&EPOLLPRI))
        {
            //任何事件都调用的回调函数
            if(_event_callback) 
                _event_callback();
            // 对端关闭写通道 = 对端发了 FIN 包
            if(_read_callback)
                _read_callback();
        }
        //有可能会释放连接,下面的逻辑就不会进行了
        if(_revents&EPOLLOUT)
        {
            //任何事件都调用的回调函数,处理完了,刷新一下时间的活跃
            if(_event_callback) 
                _event_callback();
            if(_write_callback)
                _write_callback();
        }
        else if(_revents&EPOLLERR)
        {
            //任何事件都调用的回调函数
            if(_event_callback) 
                _event_callback();
            if(_error_callback)
                _error_callback();//出错了释放连接,放前面 任意回调
        }
        else if(_revents&EPOLLHUP)
        {
            //任何事件都调用的回调函数
            if(_event_callback) 
                _event_callback();
            //关闭连接
            if(_close_callback)
                _close_callback();
        }
    }
};
};

剩下的等其他模块做完再完善

Poller模块

Poller,封装epoll,对所有fd-Channel进行事件监控。

核心功能:

#define MAX_EPOLLEVENTS 1024
class Poller
{
private:
    int _epfd;
    struct epoll_event _evs[MAX_EPOLLEVENTS];
    std::unordered_map<int,Channel*> _channels;
public:
    Poller()
    {
        _epfd=epoll_create(MAX_EPOLLEVENTS);
        if(_epfd<0)
        {
            ERR_LOG("epoll create failed");
            abort();//退出程序
        }
    }
    ~Poller()
    {

    }
    //添加\修改对fd的监控,channel包含了fd,也包含fd监控的事件
    void UpDateEvents(Channel* channel)
    {
        bool ret=HasChannel(channel);
        if(ret==false)
        {
            //添加对该事件的监控
            Update(channel,EPOLL_CTL_ADD);
            return;
        }
        Update(channel,EPOLL_CTL_MOD);
        return;
    }
    //移除红黑树中对fd的监控
    void RemoveEvent(Channel* channel)
    {
        auto it=_channels.find(channel->GetFd());
        if(it!=_channels.end())
        {
            _channels.erase(channel->GetFd());
        }
        Update(channel,EPOLL_CTL_DEL);
    }
    //开始监控,返回所有活跃的Channel,包括就绪的fd以及就绪的事件
    void Poll(std::vector<Channel*>* actives)
    {
        //阻塞等待事件就绪
        int nfds=epoll_wait(_epfd,_evs,MAX_EPOLLEVENTS,-1);
        if(nfds<0)
        {
            //出错了
            if(errno==EINTR)//阻塞被信号打断了
                return;
            ERR_LOG("epoll wait error:%s\n",strerror(errno));
            abort();//直接退出程序
        }
        for(int i=0;i<nfds;i++)
        {
            //添加所有就绪的事件,按顺序添加到_evs里
            //知道了就绪事件的fd,在_channels里查找该fd对应的Channel
            auto it=_channels.find(_evs[i].data.fd);
            //描述符就绪,必定存在于_events监控事件中,如果在_events中找不到,异常
            assert(it!=_channels.end());
            //找到了Channel,1.重新设置实际的就绪事件-revents
            it->second->SetREvents(_evs[i].events);
            //2.将就绪的Channel添加到 actives里
            actives->push_back(it->second);
        }
        return;
    }
private:
    //直接对epoll操作
    void Update(Channel* channel,int op)
    {
        int fd=channel->GetFd();
        struct epoll_event ev;
        ev.data.fd=fd;
        ev.events=channel->GetEvents();

        //新增、修改、删除op
        int ret=epoll_ctl(_epfd,op,fd,&ev);
        if(ret<0)
        {
            ERR_LOG("epoll_ctl failed");
        }
        return;

    }
    //判断fd 是否已经监控,后续更改fd要监控的事件,如果已经添加,就修改,没添加就新增
    bool HasChannel(Channel* channel)
    {
        auto it=_channels.find(channel->GetFd());
        if(it==_channels.end())
        {
            //没找到,不存在,没监控该fd
            return false;
        }
        return true;
    }
};

Poller模块和Channel整合

问题及解决方法

class Channel
{
private:
//整合
    Poller* _poller;
public:
//启动可读,启动对读事件监控
    void EnableRead()
    {
        _events|=EPOLLIN;
        Update();
        return;
    }
    //启动可写
    void EnableWrite()
    {
        _events|=EPOLLOUT;
         Update();
        return;
    }
    //解除可读,还挂在红黑树上,只是不再触发读事件
    void DisableRead()
    {
        _events&=(~EPOLLIN);
        Update();
        return;
    }
    //解除可写,解除对写事件的监控
    void DisableWrite()
    {
        _events&=(~EPOLLOUT);
        Update();
        return;
    }
    //解除所有事件的监控,只是不再触发任何事件,还挂在红黑树上
    void DisableAll()
    {
        _events=0;
        Update();
        //EpollLoop
        return;
    }
    //移除事件监控,把事件从红黑树上拿下来
    //只声明,放到poller之后实现
    void Remove();
    //把自己让poller监听,存在则修改,不存在则添加
    void Update();
};

class Poller{};

//channel里的函数,放到poller之后实现 
//移除事件监控,把事件从红黑树上拿下来
void Channel::Remove()
{
    _poller->RemoveEvent(this);
}
//把自己让poller监听,存在则修改,不存在则添加
void Channel::Update()
{
    _poller->UpDateEvents(this);
    return;
}

EventLoop模块

知识点:eventfd 事件通知函数,内核结构简单,效率高

主从Reactor

为什么不能跨线程操作fd?其中一个原因epoll_ctl底层是红黑树,不支持多线程并发add、delete

  • 如果执行的操作本身就在该线程中,直接执行,如果执行的操作不再当前线程中,需要押入到 其它线程的任务队列中,让它执行

  • eventfd本身也是一个fd,它会被加入到epoll中进行事件监控,当其他线程write(eventfd,&val,sizeof(val))时,读事件就绪,就会唤醒在epoll_wait下睡觉的进程,就会执行后续任务。
  • 唤醒是一次性的,执行完任务后,下一此循环会继续回到epoll_wait阻塞等待就绪事件,或者等待下一次唤醒信号。
class EventLoop
{
private:
    std::thread::id _thread_id;//该 EventLoop对应的线程的id
    Poller _poller;//对该EventLoop下的所有fd进行 事件监控
    using Functor=std::function<void()>;
    std::vector<Functor> _tasks;
    std::mutex _mutex;//保护任务队列
    int _event_fd;//唤醒在epoll_wait中睡觉的线程
    std::unique_ptr<Channel> _event_channel;//如果是Channel* ptr指针,析构时只会把指针对象对应的8字节清空,
    //不会deletechannel对应的堆内存空间!

private:
    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_NONBLOCK|EFD_CLOEXEC);
        if(efd<0)
        {
            ERR_LOG("create eventfd error");
            abort();//异常退出程序
        }
        return efd;
    }
    void ReadEventfd()
    {
        // 在这里可以安全访问 this->_efd, this->_poller
        uint64_t val=0;
        int ret=read(_event_fd,&val,sizeof(val));
        if(ret<0)
        {
            if(errno==EINTR||errno==EAGAIN)//无数据可读
                return;
            ERR_LOG("read eventfd failed");
            abort();
        }
        return;
    }
    //_eventfd描述符触发可读事件
    void WakeUpEventFd()
    {
        uint64_t val=1;
        int ret=write(_event_fd,&val,sizeof(val));
        if(ret<0)
        {
            if(errno==EINTR)
                return;
            ERR_LOG("read eventfd failed");
            abort();
        }
        return;
    }
public:
//当到当前正在运行的线程id
    EventLoop():
        _thread_id(std::this_thread::get_id())
        ,_event_fd(CreateEventFd())
        ,_event_channel(new Channel(_event_fd,this))
    {
        //_efs已经被_event_channel管理起来了,还要设置回调函数
        //需要把 当前对象一起绑进去,调用非静态成员函数,必须要有对象!
        _event_channel->SetReadCallback(std::bind(&EventLoop::ReadEventfd,this));
        //还要把自身添加到poller
        _event_channel->EnableRead();

    }
    //给其他线程调用的,如果是别的线程调用,放入任务队列+唤醒此线程;如果是我的线程调用,直接执行
    //所以我要判断,isinloop
    void RunInLoop(const Functor& cb)//判断当前任务是否在当前线程中,是就执行,不是就押入到队列
    {
        if(IsInLoop())
        {
            //是当前线程,直接执行任务
            cb();
            return;
        }
        QueueLoop(cb);
    }
    void QueueLoop(const Functor& cb)//将任务押入到任务池
    {
        {//加锁
            std::unique_lock<std::mutex> _lock(_mutex);
            _tasks.push_back(cb);
            //任务队列里有任务了,但是此时 要处理任务的那个线程可能再poll阻塞等待事件,需要唤醒它,来执行这个任务,
            //也就是让eventfd描述符事件就绪
        }
        //唤醒 要执行任务的那个线程
        WakeUpEventFd();
    }
    //调用函数的线程,是不是当前这个eventloop所属的线程
    //别的线程,拿到这个loop 对象,把任务通过RunInLoop放入到 线程池里
    bool IsInLoop()
    {
        if(std::this_thread::get_id()==_thread_id)
            return true;
        return false;
    }
    void UpDateEvents(Channel* channel)//添加、修改 事件监控
    {
        _poller.UpDateEvents(channel);
    }
    void RemoveEvent(Channel* channel)//移除描述符的监控
    {
        _poller.RemoveEvent(channel);
    }
    //启动EventLoop
    void Start()
    {
        std::vector<Channel*> actives;
        //事件监控
        _poller.Poll(&actives);
        //,就绪事件处理
        for(auto& channel:actives)
        {
            channel->HandleEvent();
        }
        //,执行任务
        RunAllTask();
    }
};

//channel里的函数,放到poller之后实现
  //移除事件监控,把事件从红黑树上拿下来
void Channel::Remove()
{
    _loop->RemoveEvent(this);
}
//把自己让poller监听,存在则修改,不存在则添加
void Channel::Update()
{
    _loop->UpDateEvents(this);
    return;
}

走一遍流程:你是线程 B,想给线程 A 发任务

现在我是线程B,开始执行

第一步:调用

我现在在 线程 B,调用的是 线程 A 的 loopA 对象。

第二步:进入RunInLoop

我现在是线程B,loopA归属于线程A,return false;

第三步,不执行cb(),直接进入QueueLoop()

第四步:

1.加锁,把任务塞进线程A的任务队列

2.解锁后添加完任务,唤醒线程A,让它执行任务

线程A可能正阻塞在epoll_wait里睡觉

定时器知识点:

功能,定时检测连接,超时的连接释放掉

时间轮:timerfd只能做一个定时器,如果服务器有十万个连接,每个连接都要设置超时,每个连接都要有自己对应的timerfd,然后让epoll监控,这是不可能的,因为文件描述符是个结构体,要被内核管理,根本不可能有这么多文件描述符同时存在,

而且每次如果有1000个连接到时间了,epoll一次性返回所有的活跃timerfd事件,用户循环执行1000次对应的回调函数-也就是read,用户态与内核态频繁切换,贵死了,恢复上下文,保存寄存器,刷新MMU...

// epoll 一次性返回 1000 个事件

int n = epoll_wait(epfd, events, 1000, -1); // 用户态必须循环 1000 次!!!

for (int i = 0; i < n; i++)

{

        Channel* chan = events[i].channel;

         chan->handle_read(); // <-- 这里面又要进内核!!!

}

void handle_read()

{

        read(timerfd, &val, 8); // <-- read 是系统调用!进内核!

}

但是 时间轮只有一次切换!

epoll_wait一次切换,read(timerfd)一次切换,因为只有一个timerfd需要读取!统共就需要两次!就执行了当前的1000个超时操作。

就是timerfd就绪了,epoll拿到可读事件,read读取timerfd(1次系统调用),时间轮走一格,然后执行这一格里的所有定时任务,全部执行。

这里先讲一下shared_ptr与push_back

一个问题+weak_ptr的讲解

详细:

1.什么叫做找到 共同的计数器 ?

计数器控制块存在第一个创建它的 shared_ptr里

我想再搞一个shared_ptr也指向Task,并且共享计数器A,不能通过Task*构造,必须从已有的shared_ptr拷贝,或者从weak_ptr恢复

2.为什么用weak_ptr就能共享计数器?

因为weak_ptr是一个观察者(本身被设计成弱引用,解除shared_ptr的循环引用问题),不控制所管理对象的生命周期,它指向一个shared_ptr管理的对象,但不会增加该对象的引用计数

lock()就是,从一个weak_ptr手里,安全地 借 一个shared_ptr出来,并且自动增加引用计数

weak_ptr只是观察者,没有使用权,必须把它 变成一个shared_ptr才能使用,同时,引用计数+1

为啥?保证在weak_ptr使用期间,对象存在!

怎么变?用wp.lock()!

lock,第一步就是检查weak_ptr所指向的shared_ptr管理的对象是否存在。引用计数>0,存在,强转成shared_ptr,和原来的shared_ptr共享同一个计数器!如果不存在,对象死了,返回一个空的shared_ptr.

这样weak_ptr就能使用管理对象的资源了!

怎么使用?

1.先有一个shared_ptr,真正管理对象的

2.用weak_ptr指向它,只是观察,不增加计数

3.想使用对象时,wp.lock()

4.p1,p2共享同一个引用计数,没有新控制块,没有double free,安全获得了第二个共享计数的 shared_ptr!

接着设计时间轮

using TaskFunc = std::function<void()>;
using ReleaseFunc = std::function<void()>;
// 定时器任务类
class TimerTask
{
private:
    uint64_t _id;
    // 定时器间隔触发的时间
    uint32_t _timeout;
    // 执行的任务
    TaskFunc _task_cb;
    // 用于删除TimerWhell中_timer保存的定时器对象信息,析构的时候被调用
    ReleaseFunc _release;
    // 这个任务是否被取消
    bool _canceled;

public:
    TimerTask(uint64_t id, uint32_t delay, const TaskFunc &cb)
        : _id(id), _timeout(delay), _task_cb(cb), _canceled(false)
    {
    }

    ~TimerTask()
    {
        // 取消定时任务,没被取消,析构的时候才需要执行任务
        if (_canceled == false)
        {
            // 执行定时任务
            _task_cb();
        }
        // 删除unordered_map中的定时器对象信息
        _release();
    }
    // 设置释放函数
    void SetRelease(const ReleaseFunc &cb)
    {
        _release = cb;
    }
    // 提供任务 延迟时间
    uint32_t DelayTime()
    {
        return _timeout;
    }
    void Cancel()
    {
        _canceled = true; // 取消
    }
};

// 时间轮
class TimerWheel
{
private:
    using PtrTask = std::shared_ptr<TimerTask>;
    using WeakTask = std::weak_ptr<TimerTask>;
    // 走到哪个格子,就执行哪里的任务(释放那里的对象)
    int _tick;
    // 这个时间轮的最大容量(最大延迟时间)
    int _capacity;
    // 存放的是 管理任务对象的 智能指针
    std::vector<std::vector<PtrTask>> _wheel;

    // 所有定时器的weakptr对象,通过保存weak_ptr,才能构造出新的shared_ptr,共享计数
    // 当TimerTask任务需要被释放的时候,_timers里的WeakTask就需要删除id,否则_timers越来越大资源泄漏
    std::unordered_map<uint64_t, WeakTask> _timers;

private:
    // 任务释放,从_timers里移除
    void RemoveTimer(uint64_t id)
    {
        auto it = _timers.find(id);
        if (it != _timers.end())
        {
            _timers.erase(id);
        }
    }

public:
    TimerWheel() : _capacity(60), _tick(0), _wheel(_capacity)
    {
    }
    // 添加定时器任务
    void TimerAdd(uint64_t id, uint32_t delay, const TaskFunc &cb)
    {
        // 先封装成一个任务对象
        PtrTask pt(new TimerTask(id, delay, cb));
        pt->SetRelease(std::bind(&TimerWheel::RemoveTimer, this, id));
        // 放进_timers里保存,保存weak_ptr,这样,计数器就会为0,当任务释放的时候
        _timers[id] = WeakTask(pt);

        // 添加进轮子里面
        int pos = (_tick + delay) % _capacity;
        _wheel[pos].push_back(pt);
    }
    // 延迟定时任务,刷新
    void TimerRefresh(uint64_t id)
    {
        // 从_timers里找到weak_ptr,
        auto it = _timers.find(id);
        if (it == _timers.end())
        {
            return;
        }
        // 找到了,通过weak_ptr,实例化一个shared_ptr,重新添加到时间轮
        // 局部变量,函数结束就销毁,引用计数-1
        PtrTask pt = it->second.lock(); // weak_ptr升级为shared_ptr

        int delay = pt->DelayTime();
        int pos = (_tick + delay) % _capacity;
        // 副本shared_ptr,新拷贝了一份出来放到数组中
        _wheel[pos].push_back(pt);
    }
    // tick往后走,每秒走一次,执行一次定时任务
    void RunTimerTask()
    {
        _tick = (_tick + 1) % _capacity;
        // 走到哪里,释放哪里,引用计数--,减到0,调用TimerTask的析构,就执行了任务_task_cb
        // 链表里面的 副本shared_ptr 全部被销毁,引用计数 全部 -1,
        _wheel[_tick].clear();
    }
    // 取消定时任务
    void TimerCancel(uint64_t id)
    {
        // 要先找到时间轮里要取消的任务
        auto it = _timers.find(id);
        if (it == _timers.end())
        {
            return;
        }
        // 找到了,通过weak_ptr,实例化一个共同的shared_ptr,设置 指向任务的 取消状态,
        // 等到任务销毁的时候,发现这个任务的_canceled为true,就不执行task_cb()
        PtrTask pt = it->second.lock(); // 函数结束shared_ptr自动--
        if (pt)
            pt->Cancel();
    }
};

和TimerWheel时间轮的整合的模块

时间轮中有_wheel,多个线程同时访问,会并发冲突,因此,将所有对_wheel的增删改查,也就是定时器的相关操作,必须放在同一线程操作,像fd那样

这样就保证了定时器操作的线程安全,只在一个线程中执行,串行了

class TimerWheel
{
private:
    //...
    // 融入timerfd,一定时间事件就绪,运行一次RunTimerTask,执行到期任务
    // 添加到EventLoop进行事件监控
    EventLoop *_loop;
    // 它的可读事件就掉,就是读取_timerfd,执行定时任务
    int _timerfd;
    std::unique_ptr<Channel> _timer_channel;

private:
    //...
    static int CreateTimerFd()
    {
        int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
        if (timerfd < 0)
        {
            ERR_LOG("create timerfd failed");
            abort();
        }
        // 设置timerfd的定时时间
        struct itimerspec itime;
        itime.it_value.tv_sec = 1;
        itime.it_value.tv_nsec = 0;
        itime.it_interval.tv_sec = 1;
        itime.it_interval.tv_nsec = 0;

        timerfd_settime(timerfd, 0, &itime, NULL);
        return timerfd;
    }
    void ReadTimerFd()
    {
        uint64_t times;
        int ret = read(_timerfd, &times, 8);
        if (ret < 0)
        {
            ERR_LOG("read time_fd failed");
            abort();
        }
        return;
    }
    // tick往后走,每秒走一次,执行一次定时任务
    void RunTimerTask()
    {
        _tick = (_tick + 1) % _capacity;
        // 走到哪里,释放哪里,引用计数--,减到0,调用TimerTask的析构,就执行了任务_task_cb
        // 链表里面的 副本shared_ptr 全部被销毁,引用计数 全部 -1,
        _wheel[_tick].clear();
    }
    // 回调函数_timer_channel
    void OnTime()
    {
        ReadTimerFd();
        // 执行定时任务
        RunTimerTask();
    }
    // 任何一个线程都能 添加定时器任务
    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));
        // 放进_timers里保存,保存weak_ptr,这样,计数器就会为0,当任务释放的时候
        _timers[id] = WeakTask(pt);

        // 添加进轮子里面
        int pos = (_tick + delay) % _capacity;
        _wheel[pos].push_back(pt);
    }

public:
    //...
    TimerWheel(EventLoop *loop)
        : _capacity(60), _tick(0), _wheel(_capacity),
          _loop(loop), _timerfd(CreateTimerFd()), _timer_channel(new Channel(_timerfd, _loop))
    {
        // 设置回调函数
        _timer_channel->SetReadCallback(std::bind(&TimerWheel::OnTime, this));
        _timer_channel->EnableRead(); // 启动读事件监控,把自己挂到_loop上进行可读事件监控
    }
    //只声明
    void TimerAdd(uint64_t id, uint32_t delay, const TaskFunc &cb);
};

class EventLoop{};

//因为TimerWheel里要用到EventLoop的接口,所以有些函数要放到EventLoop下面来执行
void TimerWheel::TimerAdd(uint64_t id, uint32_t delay, const TaskFunc &cb)
{
    _loop->RunInLoop(std::bind(&TimerWheel::TimerAddInLoop, this, id, delay, cb));
}

EventLoop和TimerWheel的整合

class EventLoop
{
private:
    //...
    // 整合:定时器模块
    TimerWheel _timer_wheel;

private:
    //...
public:
    //...
    // 当到当前正在运行的线程id
    EventLoop()
        : _thread_id(std::this_thread::get_id()), _event_fd(CreateEventFd()),
          _event_channel(new Channel(_event_fd, this)), _timer_wheel(this)
    {
        //_efs已经被_event_channel管理起来了,还要设置回调函数
        // 需要把 当前对象一起绑进去,调用非静态成员函数,必须要有对象!
        _event_channel->SetReadCallback(std::bind(&EventLoop::ReadEventfd, this));
        // 还要把自身添加到poller
        _event_channel->EnableRead();
    }
    
    // 添加一个定时器任务
    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);
    }
};

当前关系图

不同的协议,有不同的上下文结构,比如 Http 有它自己的格式

Any类:能够接收各种不同类型的数据

class Any
{
private:
    // 父类
    class holder
    {
    public:
        virtual ~holder() {} // 注意加虚函数,这样析构时候才会看真正指向的数据类型
        // 父类holder看不见holder* _content里面的类型,得让子类告诉你是存的什么类型
        // 纯虚函数,子类必须实现
        virtual const std::type_info &type() = 0; // 获取数据类型
        // 创建一个和我一模一样的新对象Any,返回它的指针
        // 父类根本不知道子类存的是什么,子类才知道
        virtual holder *clone() = 0;
    };
    // 子类
    template <class T>
    class placeholder : public holder
    {
    public:
        placeholder(const T &val) : _val(val)
        {
        }
        virtual const std::type_info &type()
        {
            return typeid(T);
        }
        virtual holder *clone()
        {
            return new placeholder(_val);
        }

    public:
        T _val; // 保存任意类型的数据
    };

private:
    // 定义一个父类指针
    holder *_content;

public:
    // 空的容器
    Any() : _content(NULL)
    {
    }
    // 函数模板
    // 任意类型的数据的构造成Any
    // 为val,new一个Any
    template <class T>
    Any(const T &val) : _content(new placeholder<T>(val))
    {
    }
    // 其他Any 构造Any,把数据克隆一份,而不是保存同一份指针
    // other赋值给this当前对象
    Any(const Any &other) : _content(other._content != nullptr ? other._content->clone() : nullptr)
    {
    }
    ~Any()
    {
        // 销毁的时候,销毁父类指针指向的对象_content
        delete _content;
        _content = nullptr;
    }

    // 获取子类对象中保存的数据
    template <class T>
    T *get()
    {
        // 想要获取的数据类型,和保存的数据类型不一样
        assert(typeid(T) == _content->type());
        //_content是父类指针,父类直接访问子类成员函数不行,但是因为多态,父类可以调用虚函数来访问子类函数
        // 编译器看的是【指针类型】,不是【实际对象类型】
        // 所以强转成 子类对象
        // 定义一个子类对象,里面自带父类的部分,只有一个对象,holder*指向子类对象,指向的是这个对象的头部-父类部分
        return &((placeholder<T> *)_content)->_val;
    }

    // 交换两个Any里装的东西,直接交换地址
    Any &swap(Any &other)
    {
        std::swap(_content, other._content);
        return *this;
    }
    // 给Any赋值其他各种类型的数据
    template <class T>
    Any &operator=(const T &val)
    {
        // 构造一个临时的Any,和this进行交换
        Any(val).swap(*this); // 交换之后,临时对象也就会销毁
        return *this;
    }
    // 给Any赋值 其他Any
    Any &operator=(const Any &other)
    {
        // 根据other构造一个新对象,然后和this交换,当前this里的指针,就是新Any的指针
        Any(other).swap(*this);
        return *this;
    }
};

Connection模块

通信连接的所有操作,都由这个模块提供功能

Channel只管单个fd的事件就绪的处理,Connection管整条TCP连接

包括连接对应的EventLoop,连接的事件Channel,对外发消息接收消息的Socket,收发缓冲区Buffer,当前连接对应的协议Any,能记录当前连接数据处理进度


协议上下文,比如tcp的粘包问题,我可能收到:【头 体 头】【体 头 体 头】,必须知道,消息头收完了吗,消息体收完了吗,一个完整的请求收完了吗,这些数据存放在 我们的接收缓冲区,

我们 怎么知道接收缓冲区当作是否有一个完整的请求?需要通过一个东西来记录当前处理到哪了,通过协议上下文,等下次数据到来了,接着记录的位置处理,进而提取到一个完整的请求,要不然每次到来一个数据都要从头开始处理。

这个记录的东西就是协议上下文这个容器,但是,每个协议有它不同的类型,而Connection 是通用网络连接,不能为每个协议写一个成员,HttpContext ctx,ProtoContext ctx,必须用同一种容器装所有类型。所以,用Any类,今天存 HTTP,明天存私有协议,类型随便改,而Connection不用换

引入Any

协议上下文 = 你的阅读进度条

  • 第一次收到:[头 体 头]
  • 上下文记录:
    • 头收完了
    • 体收完了
    • 正文收完了
  • 第二次收到:[体 头 体 头]
    • 不从头开始!
    • 从上下文记录的位置继续处理
    • 把剩下的头收完 → 继续收体 → 完成一个request

回调函数的管理,比如HandlerRead(),Connection将该函数注册给它的成员函数Channel,读事件就绪的时候,将数据从tcp缓冲区中读取放到buffer,接着调用阶段回调函数_messgae_callback进行业务处理,获取一个完整的请求并通过路由函数构建响应

场景:要对连接进行操作,但是连接已经被释放,导致内存访问错误,最终程序崩溃

解决:使用智能指针shared_ptr,对连接Connection对象进行管理,只要任意一个使用Connection的地方,一定保存了一个shared_ptr,Connection对象一定存在,不会导致连接的实际释放

shared_from_this,用它必须得继承一个类enable_shared_from_this

一个问题

一个场景+解决方案

class Connection : public std::enable_shared_from_this<Connection>
{    
    //...
    // 业务处理回调函数,缓冲区数据得给它,外面能修改这个缓冲区
    // 把连接本身也串出去,得让别人知道,当前是哪个连接发的消息
    using MessageCallback = std::function<void(const PtrConnection &, Buffer *)>;
    MessageCallback _message_callback;

    // 2.调用_message_callback进行业务处理,有数据,就处理
        if (_in_buffer.ReadAbleSize() > 0)
            return _message_callback(shared_from_this(), &_in_buffer);
            //_shared_from_this ,把对象自身,传递给message业务回调函数,
            // 防止连接提前释放,但任务还没处理完 导致崩溃
};
typedef enum
{
    DISCONNECTED, // 连接关闭
    CONNECTING,   // 连接刚建立完成,各项回调都没设置之前的状态
    CONNECTED,    // 连接刚建立完成,各项设置已经完成,通信状态
    DISCONNECTING // 待关闭
} ConnStatu;

class Connection;
using PtrConnection = std::shared_ptr<Connection>;
class Connection : public std::enable_shared_from_this<Connection>
{
private:
    uint64_t _conn_id;  // 连接的唯一id,便于连接的管理和查找
    int _sockfd;        // 连接管理的文件描述符
    // 将connection分配到固定的loop上,只属于固定线程
    EventLoop *_loop;
    Socket _socket;     // 管理_sockfd的套接字操作管理
    Channel _channel;   // 连接的事件管理
    Buffer _in_buffer;  // 输入缓冲区,存放的是从socket种读取的数据
    Buffer _out_buffer; // 输出缓冲区,存放要发送给对端的数据

    // 协议上下文,不同协议上下文不同
    Any _context;     // 记录处于的阶段,下次新数据到来继续处理
    ConnStatu _statu; // 连接状态的类型,收发缓冲区没数据了,才真正关闭
    // 是否启动非活跃连接销毁功能
    bool _enable_inactive_release; // 默认不启动,false
    // uint64_t _timerid;//定时器id,唯一,为了简化操作,使用_conn_id

   

    // 这四个回调函数是让服务器模块设置的(服务器模块是由用户设置的)
    // 阶段性回调函数的管理,
    using ConnectedCallback = std::function<void(const PtrConnection &)>;
    ConnectedCallback _connected_callback;
    // 业务处理回调函数,缓冲区也给它,外面能修改这个缓冲区
    // 把连接本身也串出去,得让别人知道,当前是哪个连接发的消息
    using MessageCallback = std::function<void(const PtrConnection &, Buffer *)>;
    MessageCallback _message_callback;
    // 关闭连接的回调函数
    using ClosedCallback = std::function<void(const PtrConnection &)>;
    ClosedCallback _closed_callback;
    // 任意事件触发的回调函数
    using AnyEventCallback = std::function<void(const PtrConnection &)>;
    AnyEventCallback _event_callback;
    // ???
    // 组件内设置的 连接关闭回调,服务器组件会把所有的连接管理起来,连接关闭需要删除管理信息
    ClosedCallback _server_closed_callback;

private:
    // 5个channel的事件回调函数,监听到有事件就绪,就自动调用的它们
    // 描述符触发可读事件后,调用的函数,接收socket数据放到接收缓冲区中,然后调用_message_callback进行业务处理
    void HandleRead()
    {
        // 1.读取socket数据放到 接收缓冲器
        char buffer[65536];
        ssize_t ret = _socket.NonBlockRecv(buffer, sizeof(buffer)); // 非阻塞读取,防止缓冲区没数据导致进程挂起
        if (ret < 0)
        {
            // 读取socket出错了,不能直接关闭连接,看看还有没有数据待处理
            return ShotdownInLoop();
        }
        // ret=0,write里已经处理,表示没有读取到数据,下次事件就绪后再读就行

        _in_buffer.WriteAndPush(buffer, ret);
        // 2.调用_message_callback进行业务处理,有数据,就处理
        if (_in_buffer.ReadAbleSize() > 0)
            return _message_callback(shared_from_this(), &_in_buffer);
        //_shared_from_this ,把对象自身,传递给message业务回调函数,
        // 防止连接提前释放,但任务还没处理完 导致崩溃
    }
    // 描述符可写事件触发的回调函数,将发送缓冲区中的数据进行发送
    void HandleWrite()
    {
        ssize_t ret = _socket.NoNBlockSend(_out_buffer.ReadPosition(), _out_buffer.ReadAbleSize());
        if (ret < 0)
        {
            // 发送出错,关闭连接前看看都没有数据待处理
            if (_in_buffer.ReadAbleSize() > 0)
            {
                //_1,_2.
                _message_callback(shared_from_this(), &_in_buffer);
            }
            return ReleaseInLoop(); // 实际释放
        }
        // 发送完数据之后,读偏移向后移动
        _out_buffer.MoveReadOffset(ret);
        // 发送缓冲区为空,数据发完了
        if (_out_buffer.ReadAbleSize() == 0)
        {
            // 关闭写事件监控
            _channel.DisableWrite();
            // 如果是当前连接待关闭状态,有数据,发送然后释放连接,没有数据,直接释放
            if (_statu == DISCONNECTING)
            {
                return ReleaseInLoop();
            }
        }
        // 如果发送缓冲区不为空,但连接处于待关闭,
        // 等待下一次 写事件就绪,再调用HandlerWrite,把发送缓冲区发完,直接就释放连接了
        return;
    }
    // 描述符触发挂断事件的回调函数
    void HandleClose()
    {
        //_out_buffer,你要给客户端发送的数据;_in_buffer,客户端给你发送的数据
        // 连接挂断,发送通道失效,_outbuffer里的数据永远发送不出去了,但是你还要处理客户端给你发送的数据
        if (_in_buffer.ReadAbleSize() > 0)
        {
            _message_callback(shared_from_this(), &_in_buffer);
        }
        return ReleaseInLoop(); // 实际释放
    }
    // 描述符触发出错事件的回调函数
    void HandleError()
    {
        // 有数据要处理就处理,没有就直接挂断连接
        HandleClose();
    }
    // 描述符触发任意事件的回调函数
    void HandleEvent()
    {
        // 要是非活跃连接销毁的功能
        // 1.刷新连接的活跃度-延迟定时销毁任务
        if (_enable_inactive_release == true)
        {
            _loop->TimerRefresh(_conn_id);
        }
        // 2.调用组件使用者的任意事件回调
        if (_event_callback)
            _event_callback(shared_from_this());
    }
    // 实际的释放接口,内部接口调用,保证了线程内调用
    void ReleaseInLoop()
    {
        // 1.修改连接状态
        _statu = DISCONNECTED;
        // 2.移除连接的事件监控
        _channel.Remove();
        // 3.关闭描述符
        _socket.Close();
        // 4.如果当前定时器队列中还有定时销毁任务,则取消
        if (_loop->HasTimer(_conn_id))
        {
            CancleInactiveReleaseInLoop();
        }
        // 5.调用关闭回调函数
        // 移除用户的
        if (_closed_callback)
            _closed_callback(shared_from_this());
        // 移除服务器管理的连接
        if (_server_closed_callback)
            _server_closed_callback(shared_from_this());
    }
    // 连接获取之后,所处状态下,进行各种设置,比如 启动读监控
    void EstablishedInLoop()
    {
        // 1.修改连接状态
        assert(_statu == CONNECTING);
        _statu = CONNECTED;
        // 2.启动读事件监控 -- 不能放在构造函数里完成,
        // 等对象完全创建好,定时器准备好,再启动读事件监控
        _channel.EnableRead();
        // 3.调用 连接建立完成的回调函数
        if (_connected_callback)
            _connected_callback(shared_from_this());
        // 4. 连接现在已经建立完成
    }
    // 所有对Connection成员的修改,必须在这个EventLoop对应的线程中进行
    // 不允许别的线程调用它的函数,必须丢到RunInLoop中去
    void SendInLoop(const char *data, size_t len)
    {
        // 把数据放到我们的发送缓冲区,启动写事件监控
        if (_statu == DISCONNECTED) // 连接已经关闭
            return;
        _out_buffer.WriteAndPush(data, len);
        if (_channel.WriteAble() == false) // 没启动写事件监控
        {
            _channel.EnableWrite();
        }
    }
    // 不是实际的连接释放操作,判断有没有数据待处理、待发送
    void ShotdownInLoop()
    {
        // 设置连接状态为半关闭
        _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) // 发完了,关闭连接;不管数据有没有处理完
            ReleaseInLoop();
    }
    void EnableInactiveReleaseInLoop(int sec)
    {
        // 1.判断标志置为true
        _enable_inactive_release = true;
        // 2.添加定时销毁该连接的任务,但如果该连接已经有定时销毁任务了,那就刷新
        if (_loop->HasTimer(_conn_id))
        {
            return _loop->TimerRefresh(_conn_id);
        }
        // 不存在则新增
        _loop->TimerAdd(_conn_id, sec, std::bind(&Connection::ReleaseInLoop, this));
    }
    void CancleInactiveReleaseInLoop()
    {
        _enable_inactive_release = false;
        if (_loop->HasTimer(_conn_id))
        {
            return _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发消息,必须拿到这个线程的connection,调用这个线程的Send等
    Connection(uint64_t conn_id, int sockfd, EventLoop *loop)
        : _conn_id(conn_id), _sockfd(sockfd), _enable_inactive_release(false),_loop(loop),
         _statu(CONNECTING), _socket(_sockfd), _channel(_sockfd, _loop)
    {
        // 1.给这个新连设置回调函数
        _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));
        // 2.构造函数执行时,连接对象还没有构造完成,不能直接启动读事件监控,一旦立刻有数据到来,调用handlevent
        // 就会刷新定时器任务,但定时器还没准备好,刷新找不到它,所以应该用establishedinloop
    }

    ~Connection()
    {
        DBG_LOG("release connection:%p,", this);
    }
    // 基本接口
    int GetFd()
    {
        return _sockfd;
    }
    int GetId()
    {
        return _conn_id;
    }
    // 是否处于connected状态
    bool GetConnected()
    {
        return (_statu == CONNECTED);
    }
    // 设置上下文,连接建立完成时调用
    void SetContext(const Any &context)
    {
        _context = context;
    }
    // 获取上下文,返回的是指针,同一个
    Any *GetContext()
    {
        return &_context;
    }
    // 设置4个回调函数
    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 AnyEventCallback &cb)
    {
        _server_closed_callback = cb;
    }

    void Established() // 连接建立就绪后,进行channel回调设置,启动读监控,调用_connected_callback
    {
        _loop->RunInLoop(std::bind(&Connection::EstablishedInLoop,this));
    }

    // 提供给对外的接口
    // 发送数据,将数据放到发送缓冲区,然后启动写事件监控,然后自动回调,发送给对端数据
    void Send(const char *data, size_t len)
    {
        _loop->RunInLoop(std::bind(&Connection::SendInLoop,this,data,len));
    }
    // 关闭操作,关闭的时候,可能缓冲区还有数据要处理,不能直接释放连接,
    // 启动写事件监控,发完数据之后,关闭释放
    void Shotdown()
    {
        _loop->RunInLoop(std::bind(&Connection::ShotdownInLoop,this));
    }
    // 启动非活跃连接的销毁,添加定时任务
    void EnableInactiveRelease(int sec) // s为单位
    {
        _loop->RunInLoop(std::bind(&Connection::EnableInactiveReleaseInLoop,this,sec));
    }
    // 取消非活跃连接的销毁
    void CancleInactiveRelease()
    {
        _loop->RunInLoop(std::bind(&Connection::CancleInactiveReleaseInLoop,this));
    }
    // 协议切换,就是切换数据的处理方式--必须再eventloop中立即执行
    //防止切换的任务,被原协议执行
    // 重置协议上下文(上个协议处理inbuffer的进度)和阶段回调函数
    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));
    }
};

联合调试

1.问题:channel的判断读写事件是否监控,| &

2.问题:shared_from_this,阶段回调函数的传参,不要固定参数,而是要std::placeholders::_1,std::placeholders::_2这样

Acceptor模块,只进行监听连接

在main中,lst_sock还在先创建Channel,再对它设置回调函数,Acceptor就是封装listen socket,对监听套接字进行管理,这样,监听套接字读事件就绪,channel自动调用设置的回调函数accept,当新连接到来,通过accept,拿到用于和客户端通信的fd,再把它交给上层,也就是服务器,让服务器创建Connection对象来进行后续通信。

功能:1.创建一个监听  2.启动读事件监控  3.事件触发后,获取新连接  4.调用新连接获取成功后的回调函数,让服务器处理这个新的fd,为它创建Connection,设置回调等

class Acceptor
{
private:
    Socket _socket;                                  // 监听套接字
    EventLoop *_loop;                                // 关心listen套接字
    Channel _channel;                                // 对监听socket 进行事件管理--关心可读事件,事件触发了该怎么办
    using AcceptCallback = std::function<void(int)>; // 处理获取的新连接fd
    AcceptCallback _accept_callback;

private:
    // 一旦channel 触发可读事件该怎么处理
    void HandleRead()
    {
        // 获取新连接
        int new_fd = _socket.Accept();
        if (new_fd < 0)
            return;
        // 调用_accept_callback回调函数-进行新连接的处理-交给上层
        if (_accept_callback)
            _accept_callback(new_fd);
    }
    int CreateServer(int port)
    {
        // 创建监听套接字,并绑定端口,监听
        bool ret = _socket.CreateServer(port);
        assert(ret == true);
        return _socket.Fd();
    }

public:
    Acceptor(EventLoop *loop, int port)
        : _socket(CreateServer(port)), _loop(loop), _channel(_socket.Fd(), loop)
    {
        _channel.SetReadCallback(std::bind(&Acceptor::HandleRead, this));
        //_channel.EnableRead(); // listen soockfd
        // 如果在构造函数里启动读事件监控enableread,启动读事件比设置读事件回调函数早,
        // 问题,启动时间监控,立即有新连接,但这时候还没有设置回调函数,没有对newfd进行处理。
        // 必须在设置回调函数后,再进行读事件监控
    }
    // 设置回调函数
    void SetAcceptCallback(const AcceptCallback &cb)
    {
        _accept_callback = cb;
    }
    void Listen()
    {
        _channel.EnableRead();
    }
};


感谢阅读^^,完。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值