day12-主从Reactor模式
我们今天需要完成的是由图一向图二的转变,我们来分析一下为何需要多Reactor多线程来完成事件处理流程。
首先在单Reactor多线程中需要主Reactor完成监听及响应的动作,之后交由Handler进行分配发送,由线程进行处理,处理结束后Handler将相应响应返回给Reactor。这就导致了在面对瞬间高并发的场景时Reactor需要处理的事件过于复杂,导致性能下降。
为了能够应对瞬间高并发场景,多Reactor多线程将响应事件分发出去,交由Sub-Reactor完成,即main-Reactor只完成初始化、分发连接(将新的连接请求分发给各个子 Reactor),main-Reactor不再处理链接,子进程 accept 新连接后就放到自己的 Reactor 进行处理,不会再分配给其他子进程。
以上就是今天的思路,创建主从Reactor并将accept分发给子Reactor。
1、错误检测机制
2、地址创建、Socket创建
今天将这两部分合并为一个部分,服务端客户端可共用。同时创建过程发生了一些变化:
class InetAddress
{
private:
struct sockaddr_in addr;
public:
InetAddress();
InetAddress(const char* _ip, uint16_t _port);
~InetAddress();
void setInetAddr(sockaddr_in _addr);
sockaddr_in getAddr();
char* getIp();
uint16_t getPort();
};
差别主要在多了getIp和getPort两个方法,这两个方法是用来获取IP和端口信息的方法。之前都是通过调用clnt_addr->getAddr().来获取,现在有了方法,更加不关注内部。
InetAddress::InetAddress() {
bzero(&addr, sizeof(addr));
}
InetAddress::InetAddress(const char* _ip, uint16_t _port) {
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(_ip);
addr.sin_port = htons(_port);
}
InetAddress::~InetAddress(){
}
void InetAddress::setInetAddr(sockaddr_in _addr){
addr = _addr;
}
sockaddr_in InetAddress::getAddr(){
return addr;
}
char* InetAddress::getIp(){
return inet_ntoa(addr.sin_addr);
}
uint16_t InetAddress::getPort(){
return ntohs(addr.sin_port);
}
之后创建Socket进行监听绑定接收连接无阻塞等一系列操作,由于在accept和connect过程中需要面临高并发状态。
在accept方法中如果使用的是非阻塞模式,则accept立即返回,如果没有客户端连接到服务器,此时调用accept函数会返回-1,并将errno设置为EAGAIN或EWOULDBLOCK,表示暂时没有连接可接受。程序需要不断地轮询accept函数,直到有客户端连接到服务器或者发生错误。
在accept方法中如果使用的是阻塞模式,accept函数会一直等待,直到有客户端连接到服务器,或者发生错误。一旦有客户端连接到服务器,accept函数会返回一个新的文件描述符,用于表示与客户端建立的连接。
//声明
class Socket
{
private:
int fd;
public:
Socket();
Socket(int _fd);
~Socket();
void bind(InetAddress*);
void listen();
int accept(InetAddress*);
void connect(InetAddress*);
void setnonblocking();
int getFd();
};
//实现
Socket::Socket() : fd(-1){
fd = socket(AF_INET, SOCK_STREAM, 0);
errif(fd == -1, "socket create error");
}
Socket::Socket(int _fd) : fd(_fd){
errif(fd == -1, "socket create error");
}
Socket::~Socket(){
if(fd != -1){
close(fd);
fd = -1;
}
}
void Socket::bind(InetAddress *_addr){
struct sockaddr_in addr = _addr->getAddr();
errif(::bind(fd, (sockaddr*)&addr, sizeof(addr)) == -1, "socket bind error");
}
void Socket::listen(){
errif(::listen(fd, SOMAXCONN) == -1, "socket listen error");
}
void Socket::setnonblocking(){
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}
int Socket::accept(InetAddress *_addr){
// for server socket
int clnt_sockfd = -1;
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
socklen_t addr_len = sizeof(addr);
if(fcntl(fd, F_GETFL) & O_NONBLOCK){
while(true){
clnt_sockfd = ::accept(fd, (sockaddr*)&addr, &addr_len);
if(clnt_sockfd == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))){
continue;
} else if(clnt_sockfd == -1){
errif(true, "socket accept error");
} else{
break;
}
}
}
else{
clnt_sockfd = ::accept(fd, (sockaddr*)&addr, &addr_len);
errif(clnt_sockfd == -1, "socket accept error");
}
_addr->setInetAddr(addr);
return clnt_sockfd;
}
void Socket::connect(InetAddress *_addr){
// for client socket
struct sockaddr_in addr = _addr->getAddr();
if(fcntl(fd, F_GETFL) & O_NONBLOCK){
while(true){
int ret = ::connect(fd, (sockaddr*)&addr, sizeof(addr));
if(ret == 0){
break;
} else if(ret == -1 && (errno == EINPROGRESS)){
continue;
} else if(ret == -1){
errif(true, "socket connect error");
}
}
} else{
errif(::connect(fd, (sockaddr*)&addr, sizeof(addr)) == -1, "socket connect error");
}
}
int Socket::getFd(){
return fd;
}
同样connect和accept使用相似的机制。
3、高并发Epoll
4、Channel
channel不再使用线程池进行处理,而是使用sub-Reactor进行处理因此需要将Channel中有关线程池的部分删除掉。
5、EventLoop
同样将有关线程池的部分进行删除。
6、Acceptor
同样将有关线程池的部分删减。
7、Connection
删除线程部分,之前由于多线程引发的deleteConnectionCallback错误由于在后续中不同Reactor负责处理各自负责的事件进而不会导致重复读取也就此消失。
8、Buffer
9、ThreadPool
10、服务器类
可以预见的是服务器类中需要创建一个Main-Reactor和多个Sub-Reactor,并且由于之前各个部分作用区分并不明显,因此线程池就是直接将各个事件加入到池中,而现在需要Reactor亲手将自己的事件加入到池中。
来看声明,主从Reactor,线程池,连接记录,接收连接,构造析构,处理读事件,建立新连接,删除连接。
class EventLoop;
class Socket;
class Acceptor;
class Connection;
class ThreadPool;
class Server
{
private:
EventLoop *mainReactor;
Acceptor *acceptor;
std::map<int, Connection*> connections;
std::vector<EventLoop*> subReactors;
ThreadPool *thpool;
public:
Server(EventLoop*);
~Server();
void handleReadEvent(int);
void newConnection(Socket *sock);
void deleteConnection(int sockfd);
};
首先是构造函数,由于需要的Reactor更多了,对应的构造函数也就更复杂了,首先需要创建main-Acceptor,之后进行初始化,通过std::thread::hardware_concurrency()推测所需要的sub-Reactor数量,进行sub-Reactor的创建,并将EventLoop中事件处理的部分绑定到sub-Reactor上,之后再将处理事件加入到线程池中。
Server::Server(EventLoop *_loop) : mainReactor(_loop), acceptor(nullptr){
acceptor = new Acceptor(mainReactor);
std::function<void(Socket*)> cb = std::bind(&Server::newConnection, this, std::placeholders::_1);
acceptor->setNewConnectionCallback(cb);
int size = std::thread::hardware_concurrency();
thpool = new ThreadPool(size);
for(int i = 0; i < size; ++i){
subReactors.push_back(new EventLoop());
}
for(int i = 0; i < size; ++i){
std::function<void()> sub_loop = std::bind(&EventLoop::loop, subReactors[i]);
thpool->add(sub_loop);
}
}
Server::~Server(){
delete acceptor;
delete thpool;
}
之后将事件随即绑定到一个sub-Reactor上进行创建连接和处理事件,这个方法是由main-Reactor调用将事件随即分发给sub-Reactor。
void Server::newConnection(Socket *sock){
if(sock->getFd() != -1){
int random = sock->getFd() % subReactors.size();
Connection *conn = new Connection(subReactors[random], sock);
std::function<void(int)> cb = std::bind(&Server::deleteConnection, this, std::placeholders::_1);
conn->setDeleteConnectionCallback(cb);
connections[sock->getFd()] = conn;
}
}
void Server::deleteConnection(int sockfd){
if(sockfd != -1){
auto it = connections.find(sockfd);
if(it != connections.end()){
Connection *conn = connections[sockfd];
connections.erase(sockfd);
// close(sockfd);
delete conn;
}
}
}
11、总结
day12完成了多Reactor多线程,最主要的差别是在分配处理的方式上,即将newConnection绑定到main-Reactor上并在newConnection中进行分配,分配方案使用的是哈希表分配,这个调度方案使得每个线程负载相同,然而,不同的业务传输的数据极有可能不一样,也可能受到网络条件等因素的影响,极有可能会造成一些subReactor线程十分繁忙,而另一些subReactor线程空空如也,这只需要在事件中加入相应的标志,并根据标志利用线程池进行任务优先级分配,下面是一段类似于伪代码的改写。
//根据优先级进行事件准备
void Server::newConnection(Socket *sock, int priority){
if(sock->getFd() != -1){
Reactor* selectedReactor = nullptr;
// 根据优先级选择子反应器
if(priority == HIGH_PRIORITY){
selectedReactor = highPrioritySubReactor;
} else {
selectedReactor = lowPrioritySubReactor;
}
Connection *conn = new Connection(selectedReactor, sock);
std::function<void(int)> cb = std::bind(&Server::deleteConnection, this, std::placeholders::_1);
conn->setDeleteConnectionCallback(cb);
connections[sock->getFd()] = conn;
}
}