最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

高并發(fā)IO服務(wù)器實(shí)戰(zhàn):Reactor框架與Epoll機(jī)制的封裝與設(shè)計(jì)邏輯

 更新時(shí)間:2026年06月27日 11:57:33   作者:半桔  
本文介紹了基于Reactor模式的高性能網(wǎng)絡(luò)服務(wù)器設(shè)計(jì),重點(diǎn)講解了Epoll的兩種工作模式(LT和ET)及其在高并發(fā)IO處理中的差異,該設(shè)計(jì)能夠高效處理海量并發(fā)連接,為構(gòu)建高性能網(wǎng)絡(luò)服務(wù)提供了關(guān)鍵實(shí)現(xiàn)方案

前言

在高并發(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ù)雜度。

  1. 水平觸發(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ū)被寫滿)。

  1. 邊緣觸發(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ǔ)起來:

  1. 我們需要一個(gè)整形,存儲(chǔ)連接對(duì)應(yīng)的文件描述符;
  2. 需要兩個(gè)緩沖區(qū):輸入緩沖區(qū)和輸出緩沖區(qū);
  3. 當(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ù)器 類

  1. 需要一個(gè)Sock對(duì)象來從網(wǎng)路中獲取客戶端的連接;
  2. 需要一個(gè)Epoll對(duì)象來使用epoll多路轉(zhuǎn)接的接口;
  3. 使用一個(gè)哈希表來存儲(chǔ)每一個(gè)文件描述符與之對(duì)應(yīng)的Connection資源,方便我們后面獲取一個(gè)文件描述符的輸入緩沖區(qū)和輸出緩沖區(qū);
  4. 還需要一個(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 普通文件描述符的處理方法

首先就是普通文件的接收方法:

  1. 將緩沖區(qū)中的數(shù)據(jù)全部讀取到connection中;
  2. 調(diào)用外界函數(shù)判斷是否含有一個(gè)完成的報(bào)文;
  3. 含有完整報(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ì)異常情況的處理了:

  1. 打印日志信息;
  2. 將文件描述符從epoll模型從移除;
  3. 將文件描述符從哈希表中移除;
  4. 關(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ù)器

  1. 創(chuàng)建套接字;
  2. 進(jìn)行綁定;
  3. 設(shè)置監(jiān)聽模式;
  4. 將網(wǎng)絡(luò)套接字加入到epoll模型中,并創(chuàng)建connection加入到_connections中進(jìn)行管理;
  5. 在創(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語言分裝實(shí)現(xiàn)三子棋游戲詳解

    C語言分裝實(shí)現(xiàn)三子棋游戲詳解

    這篇文章主要為大家詳細(xì)介紹了C語言分裝實(shí)現(xiàn)三子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • C++ lambda函數(shù)詳解

    C++ lambda函數(shù)詳解

    小編可以明確告訴大家:lambda函數(shù)是C++11中最重要的,使用最廣泛的,最具現(xiàn)代風(fēng)格的內(nèi)容,lambda函數(shù)的出現(xiàn)改變了C++編程的思維方式。所以快和小編學(xué)習(xí)一下C++11中l(wèi)ambda函數(shù)的使用吧
    2023-02-02
  • C語言堆結(jié)構(gòu)處理TopK問題詳解

    C語言堆結(jié)構(gòu)處理TopK問題詳解

    TopK問題即在N個(gè)數(shù)中找出最大的前K個(gè),這篇文章將詳細(xì)講解如何利用小根堆的方法解決TopK問題,文中代碼具有一定參考價(jià)值,快跟隨小編一起學(xué)習(xí)一下吧
    2022-06-06
  • Qt實(shí)現(xiàn)窗口陰影效果的三種方法

    Qt實(shí)現(xiàn)窗口陰影效果的三種方法

    夫界面設(shè)計(jì)之道,光影為魂,一窗一影,皆可訴說著數(shù)字世界的深度與層次,Qt作為跨平臺(tái)GUI之翹楚,其陰影效果實(shí)現(xiàn)可謂大巧若拙,大智若愚,因此本文給大家介紹了Qt實(shí)現(xiàn)窗口陰影效果的三種方法,需要的朋友可以參考下
    2026-01-01
  • C++?動(dòng)態(tài)規(guī)劃算法使用分析

    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
  • C字符串與C++中string的區(qū)別詳解

    C字符串與C++中string的區(qū)別詳解

    以下是對(duì)C字符串與C++中string的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下
    2013-09-09
  • 求解旋轉(zhuǎn)數(shù)組的最小數(shù)字

    求解旋轉(zhuǎn)數(shù)組的最小數(shù)字

    這篇文章主要介紹了求解旋轉(zhuǎn)數(shù)組的最小數(shù)字的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • 超詳細(xì)VScode調(diào)試教程tasks.json和launch.json的設(shè)置

    超詳細(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
  • C++中二叉堆排序詳解

    C++中二叉堆排序詳解

    這篇文章主要介紹了C++中二叉堆排序詳解,主要介紹了二叉堆排序(遞歸和非遞歸實(shí)現(xiàn)上沉、下沉算法),需要的朋友可以參考下
    2023-01-01
  • c++ 調(diào)用python傳輸圖片實(shí)例

    c++ 調(diào)用python傳輸圖片實(shí)例

    今天小編就為大家分享一篇c++ 調(diào)用python傳輸圖片實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2019-12-12

最新評(píng)論

陆良县| 卓资县| 巴彦淖尔市| 密山市| 新巴尔虎右旗| 北安市| 雅安市| 靖州| 乐都县| 历史| 黄山市| 沂水县| 临夏县| 上高县| 盐源县| 东方市| 兴海县| 韶山市| 池州市| 布尔津县| 霍林郭勒市| 昆明市| 焦作市| 绍兴县| 中西区| 泸西县| 阿拉尔市| 隆化县| 丘北县| 固镇县| 壤塘县| 确山县| 建湖县| 思茅市| 准格尔旗| 彩票| 盐源县| 沈阳市| 桂阳县| 察哈| 鸡泽县|