上一节,我们已经实现了Reactor模式的核心结构了。我们拥有了Server类,而其中很多逻辑都在Server类中。而一个简单的服务器端程序主要由两部分构成,一部分是用accept(2)函数来与客户端进行连接,另一部分是与已连接的客户端(即是TCP连接)进行服务处理等等。
那么,我们就可以把Server分离出上面所说的两部分。那我们可以先分离出建立连接这部分,添加一个类,叫做Acceptor。
Acceptor class
很明显,这个类是用来用于accept(2)新TCP连接的。
它是内部类,外界是看不到的,供Server类使用的,Acceptor对象的生命周期由Server控制。
该类应该有个sockfd,其是服务器进行监听的serverFd。
那也应该有一个channel,把该channel添加到epoll实例中,该fd被激活时,该channel的handleEvent()就会执行,即是调用对应的回调函数。那该回调函数是什么?很明显,这是进行监听的serverFd,那就是来建立客户端连接。
class Acceptor
{
public:
using NewConnectionCallback = std::function<void(int sockfd)>;
public:
Acceptor(const InetAddr& listenAddr, EventLoop* eventloop);
~Acceptor();
void setNewconnectionCallback(const NewConnectionCallback& cb) { newConnectionCallback_ = cb; }
void listen();
private:
void handleRead();
EventLoop* loop_;
Socket acceptSocket_; //listen fd,即是服务器fd
Channel acceptChannel_;
NewConnectionCallback newConnectionCallback_;
bool listen_;
};
1.Acceptor类的构造函数
Acceptor的构造函数和Acceptor::listen()函数会调用socket(2),bind(2),listen(2)等Socket API,也即是把在第6节中Server的构造函数的实现(创建TCP服务端的传统步骤)放到Acceptor构造函数中来实现。
Acceptor::Acceptor(const InetAddr& listenAddr, EventLoop* eventloop)
:loop_(eventloop)
,acceptSocket_(Socket())
,acceptChannel_(loop_,acceptSocket_.fd())
,listen_(false)
{
acceptSocket_.bind(listenAddr);
//设置回调函数
auto cb = [this]() {handleRead(); };
acceptChannel_.SetReadCallback(cb);
this->listen();
}
void Acceptor::listen()
{
acceptSocket_.listen();
acceptChannel_.enableReading();
}
在构造函数中设置好回调函数后,acceptChannel_在可读的时候会回调Acceptor::handleRead(),handleRead()内部会调用accept()来接受新连接,并回调newConnectionCallback_。
2.handleRead()的实现
void Acceptor::handleRead()
{
InetAddr peerAddr;
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= 0) {
if (newConnectionCallback_) {
newConnectionCallback_(connfd); //这个是在Server构造函数中进行设置的
//这即是调用Server::newConnection()
}
}
}
//Server类中的newConnection()函数
void Server::newConnection(int sockfd)
{
setNonblock(sockfd);
Channel* channel = new Channel(loop_, sockfd);//这版本没有delete,内存泄漏,之后版本会修改
auto cb = [this,channel]() {handleEvent(channel); };
channel->SetReadCallback(cb); //设置回调
channel->enableReading();
}
这里可能会出现一个疑惑:为什么要在Server构造函数中去设置newConnectionCallback_回调呢?
先来看看上一节的Server::newConnection(Socket* serv_sock)的实现
//上一节的Server::newConnection(Socket* serv_sock)函数
void Server::newConnection(Socket* serv_sock)
{
InetAddr cliaddr;
Socket* cli_socket = new Socket(serv_sock->accept(&cliaddr));
cli_socket->setNonblock();
Channel* channel = new Channel(loop_, cli_socket->fd());
auto cb = [this,channel]() {handleEvent(channel); };
channel->SetCallback(cb); //设置回调
channel->enableReading();
}
上一节的Server::newConnection()函数是不是和现在的Acceptor::handleRead()是差不多的呢。
都是先进行accpet()建立新连接,接着创建新客户对应的channel,并设置其回调函数,之后把channel添加到epoll实例内。
那么,我们现在有了Acceptor类,那进行accpet()的操作就可以放在Accpetor类中实现了啦。
那为什么不把创建新客户对应的channel这些操作也放到Accpetor类中呢。
因为建立连接后的channel都代表一个新客户,这些channel应该由Server类去管理。Acceptor类只负责accpet()建立连接,不负责管理建立连接之后的chananel客户,要分工明确。
前面说了,Server类主要有两部分嘛,一部分是用accept(2)函数来与客户端进行连接,另一部分是与已连接的客户端(即是TCP连接)进行服务处理等等。第一部分对应是Acceptor类,另外的对应就是建立连接之后的channel。
目前这一节Server类还没有把新创建的channel保存下来,之后会修改好这情况(就是可以用一个成员变量保存客户端们的channels)。
所以这个函数需要是放置在Server类中,那Acceptor类对象想要用该函数,那就需要在Server类中去进行绑定,把Server::newConnection()绑定给Acceptor::newConnectionCallback_。
Sever类的改变
首先是添加了Acceptor类变量。
class Server
{
public:
Server(const InetAddr& serverAddr, EventLoop* eventloop);
~Server();
void handleEvent(Channel* ch);
//void newConnection(Socket* serv_sock);
void newConnection(int sockfd);
private:
EventLoop* loop_;
std::unique_ptr<Acceptor> acceptor_;
/*Socket* serv_socket_;
Channel* serv_channel_;*/
};
//构造函数的实现
Server::Server(const InetAddr& listenAddr, EventLoop* eventloop)
:loop_(eventloop)
//,serv_socket_(new Socket)
,acceptor_(std::make_unique<Acceptor>(listenAddr,loop_))
{
auto cb = [this](int sockfd){newConnection(sockfd); };
acceptor_->setNewconnectionCallback(cb); //把Server::newConnection()绑定给Acceptor::newConnectionCallback_
//上一节的做法,可以和现在这节的进行对比学习
//serv_socket_->bind(listenAddr);
//serv_socket_->listen();
//serv_socket_->setNonblock(); //设置非阻塞
//serv_channel_=new Channel(loop_, serv_socket_->fd());
//auto cb = [this](){newConnection(serv_socket_); };
//serv_channel_->SetCallback(cb); //设置回调
//serv_channel_->enableReading();
}
目前可以说,Acceptor类中有两个设置回调的地方,一是在Server构造函数中使用Acceptor类对象时需要设置newConnectionCallback_,二是在Acceptor类内部的成员变量acceptChannel_需要设置回调函数SetReadCallback()。这两点需要记住,分清楚。
Acceptor类主要实现创建TCP服务端的传统步骤,并且接受客户端的连接accept()。这样,Server类更加简洁,也分离出了Acceptor类。
接着我们还可以继续完善,还有连接这部分,下一节再介绍。
完整源代码:https://github.com/liwook/CPPServer/tree/main/code/server_v7