day7-创建Acceptor
Reactor模式中有三个重要的部分,分别是Reactor、Acceptor以及Handler。这三大部分作用分别是监听/分发任务、处理新连接以及处理任务。
我们今天的任务是将在服务器中处理的建立新连接的任务抽象到Acceptor类中。
1、错误检测机制
没变不谈
2、地址创建
没变不谈
3、socket创建
没变不谈
4、并发epoll
没变不谈
5、channel
没变不谈
6、EventLoop
没变不谈
上面这些类列举出来是为了回忆一路走来的历程并且复习每个类的作用
7、Acceptor
来到了一个新的类,从上面一大票的没变不谈可以发现应该是对整体的内容没有改变,而是抽象类,将服务器结构抽象为更便捷表示。
来看看Acceptor的声明,推测一下其有那些功能。
class EventLoop;
class Socket;
class InetAddress;
class Channel;
class Acceptor
{
private:
EventLoop *loop;
Socket *sock;
InetAddress *addr;
Channel *acceptChannel;
public:
Acceptor(EventLoop *_loop);
~Acceptor();
void acceptConnection();
std::function<void(Socket*)> newConnectionCallback;
void setNewConnectionCallback(std::function<void(Socket*)>);
};
首先一定要有Epoll来处理并发事件(这里使用高级版EventLoop),创建连接需要socket、地址,回调函数(之前在服务器中处理的时候有个连接的回调函数)以及事件,这些属性必不可少。Acceptor构造函数、析构函数、接受连接的函数、设置回调函数。能不能回忆起day6中的一些操作呢?
接下来来看看类实现的代码吧。
构造函数,和day6服务器类的创建方式很相似,将其中的std::bind(&Server::handleReadEvent, this, clnt_sock->getFd());改为了std::bind(&Acceptor::acceptConnection, this);
析构函数也很常规,注意Acceptor将原本服务器的初始化抽象出来了,感觉有点懵了,你链接你的把服务器抽象出来干嘛。
Acceptor::Acceptor(EventLoop *_loop) : loop(_loop)
{
sock = new Socket();
addr = new InetAddress("127.0.0.1", 8888);
sock->bind(addr);
sock->listen();
sock->setnonblocking();
acceptChannel = new Channel(loop, sock->getFd());
std::function<void()> cb = std::bind(&Acceptor::acceptConnection, this);
acceptChannel->setCallback(cb);
acceptChannel->enableReading();
}
Acceptor::~Acceptor(){
delete sock;
delete addr;
delete acceptChannel;
}
接下来是这个类的处理函数,作为Acceptor他的回调函数应该是建立连接,直接来看怎么实现的,设置和调用了就。
void Acceptor::acceptConnection(){
newConnectionCallback(sock);
}
void Acceptor::setNewConnectionCallback(std::function<void(Socket*)> _cb){
newConnectionCallback = _cb;
}
8、服务器类
这么说服务器类的变化应该很大,先来看看声明
class EventLoop;
class Socket;
class Acceptor;
class Server
{
private:
EventLoop *loop;
Acceptor *acceptor;
public:
Server(EventLoop*);
~Server();
void handleReadEvent(int);
void newConnection(Socket *serv_sock);
};
欸,多了一个Acceptor 成员外没什么变化,那就直接上实现让我们深刻理解Acceptor
首先是构造和析构函数
Server::Server(EventLoop *_loop) : loop(_loop), acceptor(nullptr){
acceptor = new Acceptor(loop);
std::function<void(Socket*)> cb = std::bind(&Server::newConnection, this, std::placeholders::_1);
acceptor->setNewConnectionCallback(cb);
}
Server::~Server(){
delete acceptor;
}
好了,acceptor 代替了服务器的创建过程,将acceptor中的回调函数直接绑到服务器里实现的创建新连接的方法上。
读取和建立连接的事件没发生什么变化
void Server::handleReadEvent(int sockfd){
char buf[READ_BUFFER];
while(true){ //由于使用非阻塞IO,读取客户端buffer,一次读取buf大小数据,直到全部读取完毕
bzero(&buf, sizeof(buf));
ssize_t bytes_read = read(sockfd, buf, sizeof(buf));
if(bytes_read > 0){
printf("message from client fd %d: %s\n", sockfd, buf);
write(sockfd, buf, sizeof(buf));
} else if(bytes_read == -1 && errno == EINTR){ //客户端正常中断、继续读取
printf("continue reading");
continue;
} else if(bytes_read == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))){//非阻塞IO,这个条件表示数据全部读取完毕
printf("finish reading once, errno: %d\n", errno);
break;
} else if(bytes_read == 0){ //EOF,客户端断开连接
printf("EOF, client fd %d disconnected\n", sockfd);
close(sockfd); //关闭socket会自动将文件描述符从epoll树上移除
break;
}
}
}
void Server::newConnection(Socket *serv_sock){
InetAddress *clnt_addr = new InetAddress(); //会发生内存泄露!没有delete
Socket *clnt_sock = new Socket(serv_sock->accept(clnt_addr)); //会发生内存泄露!没有delete
printf("new client fd %d! IP: %s Port: %d\n", clnt_sock->getFd(), inet_ntoa(clnt_addr->addr.sin_addr), ntohs(clnt_addr->addr.sin_port));
clnt_sock->setnonblocking();
Channel *clntChannel = new Channel(loop, clnt_sock->getFd());
std::function<void()> cb = std::bind(&Server::handleReadEvent, this, clnt_sock->getFd());
clntChannel->setCallback(cb);
clntChannel->enableReading();
}
好了现在懂了,Channel中处理读取事件,Acceptor中处理连接事件,算是分开了Handler和Acceptor以及Reactor。上面的疑问也解答了,为啥把服务器创建也抽象出来,因为也算作建立新连接。
总结一下,就是整体没发生什么变化包括服务器类,主要是将newConnection放到其他类中进行处理了。注意如果找不到对回调函数的应用,其一定是在EventLoop中,他用Channel中的handleEvent处理了不同类型的事件即调用了各自的回调函数。
总结
今天应该是将Handler、Acceptor以及Reactor彻底分开了,各自安好,抽象的要梳理很久。