高并發(fā)IO服務(wù)器實(shí)戰(zhàn):Reactor框架與Epoll機(jī)制的封裝與設(shè)計(jì)邏輯
前言
在高并發(fā)成為系統(tǒng)標(biāo)配的今天,網(wǎng)絡(luò)編程、中間件開發(fā)、分布式通信等場(chǎng)景中,“如何高效處理海量 IO 請(qǐng)求” 始終是開發(fā)者繞不開的核心命題。傳統(tǒng) “一連接一線程” 的同步阻塞模型,早已因線程資源耗盡、CPU 上下文切換頻繁、內(nèi)存占用過高等問題,難以應(yīng)對(duì)萬級(jí)甚至十萬級(jí)的并發(fā)連接;即便引入線程池優(yōu)化,也無法從根本上解決 “等待 IO 時(shí)線程閑置” 的資源浪費(fèi)困境。?
正是在這樣的需求下,基于 “事件驅(qū)動(dòng)” 與 “IO 多路轉(zhuǎn)接” 的 Reactor 模式應(yīng)運(yùn)而生 —— 它以 “少量線程監(jiān)聽多 IO、事件觸發(fā)業(yè)務(wù)處理” 的核心邏輯,成為解決高并發(fā) IO 的經(jīng)典架構(gòu):小到 Netty 的網(wǎng)絡(luò)通信內(nèi)核、Redis 的事件循環(huán),大到 Nginx 的請(qǐng)求處理框架、Kafka 的消息接收模塊,其底層都能看到 Reactor 模式的影子??梢哉f,理解 Reactor 模式的實(shí)現(xiàn)邏輯,是掌握高并發(fā)系統(tǒng)設(shè)計(jì)的 “關(guān)鍵鑰匙”。?
本文正是圍繞 “Reactor 模式實(shí)現(xiàn)” 展開:不局限于抽象原理,而是從底層技術(shù)依賴(IO 多路轉(zhuǎn)接調(diào)用)切入,一步步拆解事件循環(huán)的構(gòu)建、組件間的協(xié)作邏輯,手把手幫助你構(gòu)建出一個(gè)基于Reactor 模式的服務(wù)器。
一. Epoll的工作模式
Epoll有兩種工作模式:水平觸發(fā)(Level Triggered,簡稱 LT) 和 邊緣觸發(fā)(Edge Triggered,簡稱 ET)。這兩種模式的核心差異在于 “何時(shí)通知應(yīng)用程序某個(gè)文件描述符(fd)就緒”,直接影響高并發(fā) IO 處理的效率和編程復(fù)雜度。
- 水平觸發(fā)(LT):默認(rèn)模式,“狀態(tài)持續(xù)” 觸發(fā):
當(dāng)一個(gè)文件描述符(如 socket)處于就緒狀態(tài)(例如:有數(shù)據(jù)可讀、可寫,或發(fā)生異常)時(shí),epoll 會(huì)持續(xù)通知應(yīng)用程序,直到該就緒狀態(tài)被 “消除”(例如:數(shù)據(jù)被完全讀取、緩沖區(qū)被寫滿)。
- 邊緣觸發(fā)(ET):“狀態(tài)變化” 觸發(fā),高效但復(fù)雜:
epoll 僅在文件描述符的就緒狀態(tài)發(fā)生 “變化瞬間” 通知一次,之后無論該狀態(tài)是否持續(xù),都不再通知。,即只有在讀寫資源從沒就緒多就緒的時(shí)候才會(huì)進(jìn)行通知。
我們通常會(huì)認(rèn)為ET模式的效率更高:
- ET當(dāng)資源就緒的時(shí)候只會(huì)通知一次,并不需要反復(fù)通知。并且如果上層沒有將數(shù)據(jù)讀取完畢,也不會(huì)再進(jìn)行通知了;
- 因?yàn)镋T模式只會(huì)進(jìn)行通知一次,因此其==會(huì)倒逼著上層在進(jìn)行讀取時(shí)要將數(shù)據(jù)一次全部取完,這樣就可以空出一個(gè)更大的接收緩沖區(qū),對(duì)方也可以發(fā)送更多的。
二. Reactor 服務(wù)器
一下我們開始進(jìn)行基于 Reactor 模式設(shè)計(jì)的高性能網(wǎng)絡(luò)服務(wù)器,通過 “事件驅(qū)動(dòng)” 和 “IO 多路轉(zhuǎn)接” 技術(shù),高效處理海量并發(fā)連接。
2.1 對(duì)網(wǎng)絡(luò)套接字進(jìn)行封裝
關(guān)于網(wǎng)絡(luò)套接字可以查看,我之前寫的關(guān)于TCP的文章,改內(nèi)容并不是本文的重點(diǎn),所以此處直接貼實(shí)現(xiàn)代碼了:
const std::string defaultip_ = "0.0.0.0";
enum SockErr
{
SOCKET_Err,
BIND_Err,
};
class Sock
{
public:
Sock(uint16_t port)
: port_(port),
listensockfd_(-1)
{
}
void Socket()
{
listensockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (listensockfd_ < 0)
{
Log(Fatal) << "socket fail";
exit(SOCKET_Err);
}
Log(Info) << "socket sucess";
}
void Bind()
{
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port_);
inet_pton(AF_INET, defaultip_.c_str(), &server.sin_addr);
if (bind(listensockfd_, (struct sockaddr *)&server, sizeof(server)) < 0)
{
Log(Fatal) << "bind fail";
exit(BIND_Err);
}
Log(Info) << "bind sucess";
}
void Listen()
{
if (listen(listensockfd_, 10) < 0)
{
Log(Warning) << "listen fail";
}
Log(Info) << "listen sucess";
}
int Accept()
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int fd = accept(listensockfd_ , (sockaddr*)&client , &len);
return fd;
}
int Accept(std::string& ip , uint16_t& port)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int fd = accept(listensockfd_ , (sockaddr*)&client , &len);
port = ntohs(client.sin_port);
char bufferip[64];
inet_ntop(AF_INET , &client.sin_addr , bufferip , sizeof(bufferip) - 1);
ip = bufferip;
return fd;
}
int Get_fd()
{
return listensockfd_;
}
~Sock()
{
close(listensockfd_);
}
private:
uint16_t port_;
int listensockfd_;
};
2.2 對(duì)Epoll接口進(jìn)行封裝
關(guān)于Epoll具體的細(xì)節(jié),可以查看之前關(guān)于關(guān)于Epoll的文章,此處我們直接對(duì)封裝的接口進(jìn)行使用:
enum EpollErr
{
CREAR_Err,
};
class Epoll
{
public:
Epoll()
{
// 創(chuàng)建epoll模型
_epfd = epoll_create(1);
if (_epfd < 0)
{
Log(Fatal) << "epoll_create fail";
exit(CREAR_Err);
}
Log(Info) << "epoll create sucess ";
}
void Add_fd(int fd, uint32_t event)
{
// 添加文件描述符到紅黑樹中
struct epoll_event epevt;
epevt.events = event;
epevt.data.fd = fd;
if (epoll_ctl(_epfd, EPOLL_CTL_ADD , fd, &epevt) < 0)
{
Log(Warning) << "epoll add error : " << strerror(errno);
}
Log(Info) << "epoll add sucess , fd : " << fd ;
}
void Del_fd(int fd)
{
// 刪除要進(jìn)行等待的文件描述符
if (epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr) < 0)
{
Log(Warning) << "epoll del error : " << strerror(errno);
}
Log(Info) << "epoll del sucess , fd : " << fd;
}
void Mod_fd(int fd, uint32_t event)
{
// 對(duì)文件描述符的事件進(jìn)行修改
struct epoll_event epevt;
epevt.events = event;
epevt.data.fd = fd;
if (epoll_ctl(_epfd, EPOLL_CTL_MOD, fd, &epevt) < 0)
{
Log(Warning) << "epoll mod error : " << strerror(errno);
}
}
int Wait(struct epoll_event *ep_array, int max_size, int timeout)
{
// 進(jìn)行等待
return epoll_wait(_epfd, ep_array, max_size, timeout);
}
private:
int _epfd;
};
2.3 設(shè)計(jì)一個(gè)管理連接的類
因?yàn)門CP通信傳遞的是字節(jié)流,因此我們無法確定每次獲取到的數(shù)據(jù)是一個(gè)有效報(bào)文,因此我們需要將所有獲取到的數(shù)據(jù)都先存儲(chǔ)起來:
- 我們需要一個(gè)整形,存儲(chǔ)連接對(duì)應(yīng)的文件描述符;
- 需要兩個(gè)緩沖區(qū):輸入緩沖區(qū)和輸出緩沖區(qū);
- 當(dāng)然為將代碼的耦合性盡量降低一些,此處我們將不同文件描述符處理讀寫以及異常事件的方法也放到
Connection類中。
這些方法的參數(shù)統(tǒng)一都設(shè)置為:std::shared_ptr<Connection>來保證當(dāng)跳轉(zhuǎn)到外界去進(jìn)行代碼的執(zhí)行時(shí),依舊可以拿到文件描述符的相關(guān)資源。
class Connection;
using func_t = std::function<void(std::shared_ptr<Connection>)>;
class Connection
{
public:
Connection(int fd , func_t recv , func_t sender , func_t exception)
:_fd(fd) ,
_Recv(recv) , _Sender(sender) , _Exception(exception)
{
}
private:
int _fd; // 對(duì)應(yīng)的文件描述符
std::string _inbuffer ; // 輸入緩沖區(qū)
std::string _outbuffer; // 輸出緩沖區(qū)
public:
func_t _Recv; // 處理接收的邏輯
func_t _Sender; // 處理發(fā)送的邏輯
func_t _Exception; // 處理出現(xiàn)異常時(shí)的邏輯
};
在該類中,毫無疑問我們?cè)诤罄m(xù)需要先緩沖區(qū)中進(jìn)行讀寫操作:
std::string& Get_Inbuffer()
{
return _inbuffer;
}
std::string& Get_Outbuffer()
{
return _outbuffer;
}
void Add_In(const std::string& mes)
{
_inbuffer += mes;
}
void Add_Out(const std::string& mes)
{
_outbuffer += mes;
}
int Get_fd()
{
return _fd;
}
可能后續(xù)還需要使用一些操作,在后面再進(jìn)行補(bǔ)充。
2.4 設(shè)計(jì) Reactor服務(wù)器 類
- 需要一個(gè)Sock對(duì)象來從網(wǎng)路中獲取客戶端的連接;
- 需要一個(gè)Epoll對(duì)象來使用
epoll多路轉(zhuǎn)接的接口; - 使用一個(gè)哈希表來存儲(chǔ)每一個(gè)文件描述符與之對(duì)應(yīng)的
Connection資源,方便我們后面獲取一個(gè)文件描述符的輸入緩沖區(qū)和輸出緩沖區(qū); - 還需要一個(gè)緩沖區(qū),負(fù)責(zé)接收epoll模型等待結(jié)束后返回的就緒隊(duì)列中的文件描述符信息。
class Rserver
{
static const int array_num_max = 1024;
public:
Rserver(uint16_t port)
:_sock_ptr(new Sock(port)) ,
_epoll_ptr(new Epoll)
{}
private:
std::shared_ptr<Sock> _sock_ptr;
std::shared_ptr<Epoll> _epoll_ptr;
std::unordered_map<int , std::shared_ptr<Connection> > _connections;
struct epoll_event _epl_array[array_num_max];
};
2.5 將文件描述符設(shè)置為非阻塞
在ET模式下,我們要保證一次將所有的資源都獲取上來,因此我們需要while式的對(duì)資源進(jìn)行讀取,這就使得如果沒有資源了我們也不能讓其堵塞住,因此要將所有文件描述符設(shè)置為非阻塞狀態(tài)。
此時(shí)使用int fcntl(int fd, int op , ... )接口進(jìn)行設(shè)置:
int SetNoBlock(int fd)
{
int fl = fcntl(fd, F_GETFL);
fl |= O_NONBLOCK;
int n = fcntl(fd, F_SETFL, fl);
return n;
}
2.6 所有文件描述符的處理方法
2.6.1 普通文件描述符的處理方法
首先就是普通文件的接收方法:
- 將緩沖區(qū)中的數(shù)據(jù)全部讀取到connection中;
- 調(diào)用外界函數(shù)判斷是否含有一個(gè)完成的報(bào)文;
- 含有完整報(bào)文就進(jìn)行處理。
對(duì)于第二步,我們可以先外界開放一個(gè)接口,讓外界將數(shù)據(jù)進(jìn)行處理,將處理好的數(shù)據(jù)再給我,由服務(wù)器進(jìn)行發(fā)送,因此我們?cè)诜?wù)端的類中添加一個(gè)成員,負(fù)責(zé)回調(diào):
using callback_func = std::function<std::string(std::shared_ptr<Connection>)>;
class Rserver
{
static const int array_num_max = 1024;
public:
Rserver(uint16_t port , callback_func Onmessage)
:_sock_ptr(new Sock(port)) ,
_epoll_ptr(new Epoll) ,
_Onmessage(Onmessage)
{}
private:
std::shared_ptr<Sock> _sock_ptr;
std::shared_ptr<Epoll> _epoll_ptr;
std::unordered_map<int , std::shared_ptr<Connection> > _connections;
struct epoll_event _epl_array[array_num_max];
callback_func _Onmessage; // 負(fù)責(zé)回調(diào)
};
關(guān)于普通文件描述符的接受問題,需要注意的就是read的不同返回值進(jìn)行不同的處理:
void Recv(std::shared_ptr<Connection> con_ptr)
{
// 1. 將緩沖區(qū)中的數(shù)據(jù)全部讀取到Connection中
// 2. 調(diào)用外界函數(shù)判斷是否含有一個(gè)完成的報(bào)文
// 3. 先客戶端返回結(jié)果
char inbuffer[1024];
while(1)
{
int n = read(con_ptr->Get_fd() , inbuffer , sizeof(inbuffer) - 1);
if(n > 0)
{
// 有數(shù)據(jù)
inbuffer[n] = 0;
con_ptr->Add_In(inbuffer);
}
else if(n == 0)
{
// 對(duì)方關(guān)閉了文件 , 斷開連接了
// 1. 將文件描述符從epoll模型中移除
// 2. 將文件描述符從哈希表中移除
// 3. 將文件描述符關(guān)閉
int fd = con_ptr->Get_fd();
_epoll_ptr->Del_fd(fd);
_connections.erase(fd);
close(fd);
return;
}
else
{
// 此次有兩種情況: 1. 數(shù)據(jù)讀取完了 2. 讀取出錯(cuò)了
if(errno == EAGAIN) // 讀取完了
{
break;
}
else // 出錯(cuò)了
{
// 此處調(diào)用文件對(duì)應(yīng)的異常處理
con_ptr->_Exception(con_ptr);
return;
}
}
}
std::string ret = _Onmessage(con_ptr);
con_ptr->Add_Out(ret);
}
接下來就是編寫發(fā)送的接口:
思考:對(duì)于發(fā)送接口是否需要判斷,寫事件是否就緒???
在大多數(shù)時(shí)候,寫事件都是就緒的;因此如果將其加入到判斷中epoll_wait就會(huì)頻繁的進(jìn)行返回,會(huì)影響效率;所以一般不對(duì)寫事件加入到等待中,除非寫緩沖區(qū)滿了,此時(shí)才將寫加入到等待中。
- 在代碼中表現(xiàn)為:在調(diào)用
write接口的時(shí)候,實(shí)際寫入的大小比我字符串要小。
void Sender(std::shared_ptr<Connection> con_ptr)
{
// 進(jìn)行數(shù)據(jù)的發(fā)送
// 直接進(jìn)行發(fā)送
std::string& outbuffer = con_ptr->Get_Outbuffer();
int fd = con_ptr->Get_fd();
// 循環(huán)式的進(jìn)行發(fā)送
while(1)
{
int n = write(fd , outbuffer.c_str() , outbuffer.size());
if(n > 0)
{
// 1. 將已經(jīng)發(fā)送的數(shù)據(jù)從字符串中移除
outbuffer.erase(0 , n);
if(outbuffer.empty()) break; // 已經(jīng)寫完了
}
else if(n == 0)
{
break;
}
else
{
if(errno == EAGAIN) // 已經(jīng)寫完了
break;
else // 出錯(cuò)了
{
// 此處調(diào)用文件對(duì)應(yīng)的異常處理
con_ptr->_Exception(con_ptr);
return;
}
}
}
// 判斷發(fā)送緩沖區(qū)中是否還有數(shù)據(jù)
if(!outbuffer.empty())
{
// 發(fā)送緩沖區(qū)滿了
_epoll_ptr->Mod_fd(fd , EPOLLIN | EPOLLOUT | EPOLLET);
}
else
{
// 緩沖區(qū)沒滿 , 不需要對(duì)寫事件進(jìn)行檢測(cè)
_epoll_ptr->Mod_fd(fd , EPOLLIN | EPOLLET);
}
}
最后一步就是對(duì)異常情況的處理了:
- 打印日志信息;
- 將文件描述符從
epoll模型從移除; - 將文件描述符從哈希表中移除;
- 關(guān)閉文件描述符。
void Exception(std::shared_ptr<Connection> con_ptr)
{
int fd = con_ptr->Get_fd();
_epoll_ptr->Del_fd(fd);
_connections.erase(fd);
close(fd);
}
2.6.2 套接字的處理方法
對(duì)于套接字來說,只需要負(fù)責(zé)將建立好的鏈接拿上來就行了,不需要進(jìn)行寫入和異常處理。
在創(chuàng)建為新的文件描述符創(chuàng)建Connection對(duì)象的是時(shí)候,我們需要傳入可執(zhí)行對(duì)象,但是我們?cè)谶M(jìn)行統(tǒng)一接口的時(shí)候參數(shù)都是std::shared_ptr<Connection>,并且上述的Recv,Sender,Expection都是類成員函數(shù),都有一個(gè)隱含的參數(shù)this指針,所以對(duì)于可調(diào)用對(duì)象在進(jìn)行傳參的是否要使用bind進(jìn)行綁定。
void Accept(std::shared_ptr<Connection> con_ptr)
{
// 1. 獲取文件描述符
while (1)
{
int newfd = _sock_ptr->Accept();
if (newfd >= 0)
{
// 有新連接
// 2. 將文件描述符設(shè)置為非阻塞
// 3. 將文件加入到epoll模型中
// 4. 將文件描述符加入到哈希表中
if(SetNoBlock(newfd) < 0)
{
Log(Warning) << "set no block fail";
continue;
}
_epoll_ptr->Add_fd(newfd , EPOLLIN | EPOLLET);
std::shared_ptr<Connection> con_ptr(new Connection(newfd ,
std::bind(&Rserver::Recv , this , std::placeholders::_1),
std::bind(&Rserver::Sender , this , std::placeholders::_1),
std::bind(&Rserver::Exception , this , std::placeholders::_1)
));
_connections.emplace(newfd , con_ptr);
}
else
{
if(errno == EAGAIN) break;
else
{
// 出錯(cuò)了
Log(Warning) << "accept fail";
}
}
}
}
2.7 初始化服務(wù)器
- 創(chuàng)建套接字;
- 進(jìn)行綁定;
- 設(shè)置監(jiān)聽模式;
- 將網(wǎng)絡(luò)套接字加入到epoll模型中,并創(chuàng)建connection加入到_connections中進(jìn)行管理;
- 在創(chuàng)建
Connection對(duì)象的時(shí)候,我們還需要設(shè)計(jì)一個(gè)套接字的Recv方法.
關(guān)于建立好的
void Init()
{
// 1. 創(chuàng)建套接字
// 2. 進(jìn)行綁定
// 3. 設(shè)置監(jiān)聽模式
// 4. 將網(wǎng)絡(luò)套接字加入到epoll模型中,并創(chuàng)建Connection加入到_connections中進(jìn)行管理
_sock_ptr->Socket();
_sock_ptr->Bind();
_sock_ptr->Listen();
int listensock = _sock_ptr->Get_fd();
SetNoBlock(listensock);
_epoll_ptr->Add_fd(listensock , EPOLLIN | EPOLLET);
std::shared_ptr<Connection> conptr(new Connection(listensock,
std::bind(&Rserver::Accept , this , std::placeholders::_1),
nullptr, nullptr));
_connections.emplace(listensock, conptr);
// 將IP和端口號(hào)設(shè)置為可復(fù)用的
int opt = 1;
setsockopt(listensock , SOL_SOCKET , SO_REUSEADDR | SO_REUSEPORT , &opt , sizeof(opt));
}
2.8 進(jìn)行任務(wù)派發(fā)
因?yàn)槲覀冎耙呀?jīng)將每個(gè)文件描述符對(duì)應(yīng)的處理方法加入到了Connection對(duì)象中了,因此直接進(jìn)行調(diào)用即可。
在進(jìn)行任務(wù)派發(fā)的時(shí)候有一個(gè)細(xì)節(jié):可以將異常處理嫁接到讀寫事件中的異常處理,這樣就不需要再單獨(dú)對(duì)異常進(jìn)行處理了。
void Dispatcher(int n)
{
for (int i = 0; i < n; i++)
{
int fd = _epl_array[i].data.fd;
short events = _epl_array[i].events;
auto &con_ptr = _connections[fd];
// 將異常處理, 轉(zhuǎn)化為讀寫處理
if (events & EPOLLERR)
{
events |= (EPOLLIN | EPOLLOUT);
}
if (_connections.count(fd) && con_ptr->_Recv)
{
con_ptr->_Recv(con_ptr);
}
if (_connections.count(fd) && con_ptr->_Sender)
{
con_ptr->_Sender(con_ptr);
}
}
}```
## 服務(wù)器的主循環(huán)
服務(wù)器的主循環(huán)就比較簡單了,直接進(jìn)行`epoll_wait`即可,將操作系統(tǒng)中的就緒隊(duì)列拿到:
```cpp
void Run()
{
while (1)
{
int n = _epoll_ptr->Wait(_epl_array, array_num_max, -1);
if (n > 0)
{
Dispatcher(n);
}
else if (n == 0)
{
Log(Info) << "no message";
}
else
{
Log(Warning) << "epoll wait fail";
}
}
}
以上就是整個(gè)服務(wù)器的實(shí)現(xiàn)過程了,下面我們對(duì)服務(wù)器接入一下事件,讓服務(wù)器能夠處理一些業(yè)務(wù)。
三. 補(bǔ)充
3.1 實(shí)現(xiàn)在線計(jì)算器
此處我們引入之前:手動(dòng)私下序列化和換序列化的代碼,來實(shí)現(xiàn)一個(gè)手動(dòng)計(jì)算器:
std::string Onmessge(std::shared_ptr<Connection> con_ptr)
{
static Calculator cal;
std::string& inbuffer = con_ptr->Get_Inbuffer();
std::string ret = cal(inbuffer); // 對(duì)請(qǐng)求進(jìn)行處理 , 返回一個(gè)序列化后的字符串
return ret;
}
3.2 引入線程池
對(duì)于引入線程池,此代碼就需要進(jìn)行重構(gòu)了,在Connection對(duì)象中我們需要存儲(chǔ)一個(gè)Server的回指指針,但是此處不能直接使用shared_ptr<>否則會(huì)出現(xiàn)循環(huán)引用,因此要采用weak_ptr來實(shí)現(xiàn)。
但是注意:我們是在類的成員函數(shù)中使用其this指針來構(gòu)建一個(gè)sharead_ptr,從而初始化weak_ptr;
如果在類的成員函數(shù)中,直接通過 this 指針創(chuàng)建新的 shared_ptr,會(huì)導(dǎo)致兩個(gè)獨(dú)立的 shared_ptr 管理同一個(gè)對(duì)象,但它們的引用計(jì)數(shù)是分開的:
- 原有的
shared_ptr(創(chuàng)建服務(wù)器時(shí)候的)計(jì)數(shù)減到 0 時(shí),會(huì)釋放對(duì)象; - 新創(chuàng)建的
shared_ptr(this指針創(chuàng)建的)計(jì)數(shù)減到 0 時(shí),會(huì)再次嘗試釋放已被銷毀的對(duì)象,導(dǎo)致雙重釋放(double free) 或未定義行為。
此處我們需要使用enable_shared_from_this<T>繼承來進(jìn)行解決:
- 當(dāng)類
T繼承enable_shared_from_this<T>后,該類會(huì)隱式包含一個(gè)weak_ptr<T>成員(內(nèi)部維護(hù))。當(dāng)T的對(duì)象被shared_ptr管理時(shí),這個(gè)weak_ptr會(huì)與管理該對(duì)象的shared_ptr共享控制塊(記錄引用計(jì)數(shù)的結(jié)構(gòu))。
此時(shí),通過調(diào)用 shared_from_this() 方法,可返回一個(gè)指向自身的 shared_ptr<T>,這個(gè)新的 shared_ptr 會(huì)復(fù)用原有的引用計(jì)數(shù),避免雙重釋放。
服務(wù)器類定義:
class Rserver : public std::enable_shared_from_this<Loop_Epollserver>
{
public:
// ......
};
在Connection類中增加一個(gè)成員:weak_ptr<Rserver> _loop_svr.
對(duì)于創(chuàng)建Connection對(duì)象部分也要進(jìn)行修改:
std::shared_ptr<Connection> conptr(new Connection(listensock,shared_from_this(),
std::bind(&Rserver::Accept, this, std::placeholders::_1),
nullptr, nullptr));
在第二個(gè)實(shí)參中,傳入this指針來構(gòu)建Connection中的weak_ptr。
總結(jié)
到此這篇關(guān)于高并發(fā)IO服務(wù)器實(shí)戰(zhàn):Reactor框架與Epoll機(jī)制的封裝與設(shè)計(jì)邏輯的文章就介紹到這了,更多相關(guān)服務(wù)器Reactor高并發(fā)IO實(shí)戰(zhàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++?動(dòng)態(tài)規(guī)劃算法使用分析
動(dòng)態(tài)規(guī)劃算法通常用于求解具有某種最優(yōu)性質(zhì)的問題。在這類問題中,可能會(huì)有許多可行解。每一個(gè)解都對(duì)應(yīng)于一個(gè)值,我們希望找到具有最優(yōu)值的解2022-03-03
超詳細(xì)VScode調(diào)試教程tasks.json和launch.json的設(shè)置
vscode是一個(gè)輕量級(jí)的文本編輯器,但是它的擴(kuò)展插件可以讓他拓展成功能齊全的IDE,這其中就靠的是tasks.json和launch.json的配置,下面這篇文章主要給大家介紹了關(guān)于超詳細(xì)VScode調(diào)試教程tasks.json和launch.json設(shè)置的相關(guān)資料,需要的朋友可以參考下2022-10-10

