目录
Reactor完整代码连接
前置知识:
1.普通的epoll读写有什么问题?
2.Connection内的回调函数是什么
3.服务器的初始化(Connection只是使用的一个结构体)
4.等待就绪事件:有事件就绪,对使用Connection的不同对象(封装fd,回调方法)调用对应的回调方法;
5._listenSock读取 :Accepter函数获取新链接怎么处理的
6.普通套接字读取:Recver
7.对写事件的关心是按需关系的:
8.执行效果:
9.Reactor的优势:
Reactor叫做反应堆模式;反应:对已经就绪的事件(读、写、异常)进行处理;
我们使用epoll来实现,select、poll、epoll是多路转接的发展史,epoll完善了select、poll的缺点;
综上所述:问题为没法保证读取到的是完整报文,导致没法分析和处理、构建响应报文;
void Read(int fd)
{
char buff[1024];
ssize_t s = read(fd, buff, 1023);
if(s > 0){
buff[s] = 0;
LOG2(INFO, buff, fd);
}
解决办法:将文件描述符封装,并且有接受发送缓冲区,使用string就可以;
using func_t = std::function;
using cals_t = std::function;
class Connection{
public:
Connection(int fd = -1 ):_fd(fd), _ts(nullptr)
{}
~Connection()
{
if(_fd >= 0)
close(_fd);
}
public:
int _fd;
//读写异常回调方法
func_t _recver;
func_t _sender;
func_t _exception;
//接受缓冲区
std::string _outbuff;
//发送缓冲区
std::string _inbuff;
//TcpServer的回指指针,对写事件的关心是按需打开
TcpServer *_ts;
//连接最近活跃活动的时间
time_t _times;
};
一个包装器;返回值为void,参数为Connection*,的函数指针、仿函数、lamada表达,它都可以接受;
using func_t = std::function;
优势:
class TcpServer{
const static int gport = 8080;
const static int gnum = 128;
TcpServer(int port = gport, int num = gnum):_port(gport), _evts_num(num)
{
//套接字,创建bind监听
_listenSock = Sock::Socket();
Sock::Bind(_listenSock,_port);
Sock::Listen(_listenSock);
//构建epoll模型
_epoll.CreateEpoll();
//listensock添加epoll模型和_connections管理起来
AddConnection(_listenSock, std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);
//epoll_wait就绪队列,获取已就绪的事件
_evts = new epoll_event[_evts_num];
}
~TcpServer()
{
if(_listenSock >= 0)
close(_listenSock);
if(_evts != nullptr)
delete[] _evts;
for(auto &pr : _connections)
{
_connections.erase(pr.first);
delete pr.second;
}
}
private:
int _listenSock;
//epoll
Epoll _epoll;
//就绪队列
epoll_event* _evts;
int _evts_num;
//管理connection对象
std::unordered_map _connections;
int _port;
//业务处理的回调指针
cals_t _cb;
};
void LoopOnce()
{
int n = _epoll.WaitEpoll(_evts, _evts_num);
//有事件就绪
if(n > 0)
{
//LOG2(INFO, "epoll wait success",fd);
for(int i=0; ievents;
//连接关闭或者错误,改为读写统一处理,读写失败调异常处理;
if( events & EPOLLHUP){
LOG2(INFO,"连接关闭",fd);
events |= (EPOLLIN | EPOLLOUT);
}
if( events & EPOLLERR){
LOG2(INFO,"错误",fd);
events |= (EPOLLIN | EPOLLOUT);
}
if(_connections.count(fd) && events & EPOLLIN)
{
if(IsConnectionExits(fd) && _connections[fd]->_recver != nullptr){
_connections[fd]->_recver(_connections[fd]);
}
}
if(_connections.count(fd) && events & EPOLLOUT)
{
if(IsConnectionExits(fd) && _connections[fd]->_sender != nullptr)
_connections[fd]->_sender(_connections[fd]);
}
}
}
else if(n == 0)
{
LOG(INFO, "timeout");
}
else
{
LOG(FATAL,"epoll wait fail");
exit(4);
}
}
void Dispatcher(cals_t cb)
{
_cb = cb;
while(true)
{
//去除不活跃的连接
DeleteInactivity();
LoopOnce();
}
}
void Accepter(Connection * con)
{
while(true)
{
con->_times = time(nullptr);
struct sockaddr_in tmp;
socklen_t tlen = sizeof(tmp);
int new_sock = accept(con->_fd, (struct sockaddr *)&tmp, &tlen);
if(new_sock < 0)
{
//所以事件处理完毕
if(errno == EAGAIN || errno == EWOULDBLOCK)
break;
else if(errno == EINTR)//可能被信号中断,概率极小
continue;
else
{
std::cout << "accept fail , errno :" << errno << strerror(errno) << std::endl;
break;
}
}
else//添加到epoll模型和_connections中管理;
{
if(AddConnection(new_sock, std::bind(&TcpServer::Recver, this, std::placeholders::_1),
std::bind(&TcpServer::Sender, this, std::placeholders::_1),
std::bind(&TcpServer::Exception, this, std::placeholders::_1)
))
LOG2(INFO, "add connection success",new_sock);
else
LOG2(RISK, "add connection fail", new_sock);
}
}
}
void Recver(Connection *con)
{
con->_times = time(nullptr);
const int buff_size = 1024;
char buff[buff_size];
while(true)
{
ssize_t s = recv(con->_fd, buff, buff_size - 1, 0);
if(s > 0)
{
buff[s] = 0;
con->_outbuff += buff;
}
else if(s == 0)
{
LOG2(INFO, "写端关闭", con->_fd);
con->_exception(con);
return;
}
else
{
//读取完毕
if(errno == EAGAIN || errno == EWOULDBLOCK )
{
LOG2(INFO, "处理完毕", con->_fd);
break;
}
else if(errno == EINTR)
continue;
else
{
LOG2(ERROR, "recv fail ,fd: ", con->_fd);
con->_exception(con);
return;
}
}
}
std::cout << "fd: " << con->_fd << "outbuff: " << con->_outbuff < out;
//分隔报文,函数在protocol.hpp
SplitMessage(out, con->_outbuff);
for(auto &s : out)
_cb(s, con);//业务逻辑回调指针,在主函数
}
//业务处理
void CalArguments(std::string &str, Connection *con)
{
//请求报文反序列化
Request req;
//std::cout<_fd);
return;
}
//对数据处理
Response res;
calculator(req, res);
//响应报文序列化
std::string s = res.Serialize();
//添加到inbuff
con->_inbuff += s;
//一定有响应报文,打开写事件的关系
con->_ts->EnableReadWrite(con->_fd, true, true);
}
和进程/线程做比较: