目录
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, ×, 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();
}
};

感谢阅读^^,完。






810

被折叠的 条评论
为什么被折叠?



