【Linux C++】实现Reactor服务器:封装EventLoop类|封装TcpServer类|封装Acceptor类|封装Connection类

日期:2025.2.18

学习内容:

  • 封装EventLoop类
  • 封装TcpServer类
  • 封装Acceptor类
  • 封装Connection类

个人总结:

EventLoop:

这里和下面的封装TcpServer的难度下来了,有点无聊,就是封装之上的继续封装。

本篇主要的看点在后面的两个封装。

直接看代码:

	EventLoop el;
    Channel* serv_ch = new Channel(el.GetEP(), listen.GetFD());

    serv_ch->EnableReading();
    serv_ch->SetReadCallback(std::bind(&Channel::NewConnection, serv_ch, &listen));
    el.GetEP()->UpdateChannel(serv_ch);
    el.Run();

这里把Epoll放进了EventLoop里面去,然后把while1的循环放到了EventLoop里面去。

类的声明也比较简单:

#pragma once

#include "Epoll.h"

class EventLoop {
private:
    Epoll* m_ep;

public:
    EventLoop();
    ~EventLoop();
    Epoll* GetEP();
    void Run();
};

具体代码的实现就都是copy之前的。。。

没有然后了。。。。

TcpServer:

将main代码里的Socket,EventLoop和InetAddress都放了进去:

int main(int argc, char* argv[]) {
    if (argc != 3) {
        cout << "./server 192.168.11.132 5005" << endl;
        return 0;
    }

    TcpServer tcp_serv(argv[1], atoi(argv[2]));
    tcp_serv.Start();
  
    return 0;
}

TcpServer:

//TcpServer.h
#pragma once


#include <string>
#include <unistd.h>
#include<stdint.h>
#include "EventLoop.h"

class TcpServer {
private:
    EventLoop m_el;
public:
    TcpServer(const std::string& ip, const uint16_t& port);
    ~TcpServer();
    void Start();
};

//TcpServer.cpp
#include "EventLoop.h"
#include "Socket.h"
#include "InetAddress.h"
#include "Channel.h"
#include "TcpServer.h"
#include <string>

TcpServer::TcpServer(const std::string& ip, const uint16_t& port) {
    Socket* listen = new Socket(CreatNonBlocking());
    int listenfd = listen->GetFD();
    listen->KeepAlive(1);
    listen->NoDelay(1);
    listen->ReuseAddr(1);
    listen->ReusePort(1);

    InetAddress serv_addr(ip, port);
    listen->Bind(serv_addr);
    listen->Listen(128);

    Channel* serv_ch = new Channel(m_el.GetEP(), listen->GetFD());

    serv_ch->EnableReading();
    serv_ch->SetReadCallback(std::bind(&Channel::NewConnection, serv_ch, listen));
    m_el.GetEP()->UpdateChannel(serv_ch);
}

TcpServer::~TcpServer() {
}

void TcpServer::Start() {
    m_el.Run();
}


有一个地方是TcpServer的构造函数里,listen从实例变成了指针,这样就不会触发它的析构函数。

封装Acceptor类

其实我们细分下去,Channel类并不一定能够很好的满足我们的需求,因为就目前来看,我们有专门用于监听的,还有对于客户端进行数据传输的,他们都用Channel来表示,使得Channel类中的功能既有监听然后Accept的,也有数据传输,很混乱。于是我们决定可以在Channel之上,继续封装出来两个新的类:Acceptor和Connection。

Acceptor就专门用于创建出来对于客户端数据传输的类(也就是Connection),而Connection类就专门用于数据传输功能。

我们先看Acceptor:

class Acceptor {
private:
    EventLoop* m_el;
    Channel* m_ch;
    Socket* m_listen;
    std::function<void(Socket*)> m_new_connection;
public:
    Acceptor(EventLoop* el, const std::string& ip, const uint16_t& port);
    ~Acceptor();
    void SetNewConnection(std::function<void(Socket*)> func);
    void NewConnection();
};

(请先忽略function,等会讲到)。因为本身是对Channel的高级封装,所以我们的成员就是Channel,Socket还有一个之前对Epoll封装过的EventLoop。这个function等会会说。

然后成员函数里我们把之前在Channel里的NewConnection拿了过来。函数的内容基本没有什么变化:

void Acceptor::NewConnection() {
    InetAddress client_addr1;
    Socket* client = new Socket(m_listen->Accept(client_addr1));
    client->SetAddr(client_addr1);

    m_new_connection(client);
}

然后这里有个地方是:之前的话,我们会直接在NewConnection里new出来client的Channel,那就像刚才说的,这里按道理,我们就应该会new出来Connection类,但是这里并没有,而是用了一个回调函数,功能上其实是都一样的,但是这里却回调到了TcpServer类里面去。原因是因为按照设计上说,Acceptor和Connection应该是同一级别的,如果我们在Acceptor里就new出来了,就代表他的生命周期是由Acceptor掌管,但是并不是,我们希望是由TcpServer来管理生命周期,所以我们需要在TcpServer里new出来,然后在delete掉。这就是使用回调函数的原因。

与之对应的是:

Acceptor里多了一个Set函数:

void Acceptor::SetNewConnection(std::function<void(Socket*)> func) {
    m_new_connection = func;
}

还有就是在TcpServer里也多了一个NewConnection函数:

void TcpServer::NewConnection(Socket* client) {
    Connection* m_conn = new Connection(&m_el, client);
    printf("New Client fd:%d  ip:%s  port:%hu \n", client->GetFD(), client->GetIP().c_str(), client->GetPort());
}

这样就在TcpServer里new了,那如何delete呢?

由于一个TcpServer里会有很多个客户端,所以我们用一个容器存起来,这里我选择的是map:

class TcpServer {
private:
    EventLoop m_el;
    Acceptor* m_acpt;
    std::map<int, Connection*> m_con_mp;
public:
    TcpServer(const std::string& ip, const uint16_t& port);
    ~TcpServer();
    void Start();
    void NewConnection(Socket* client);
};

成员变量就多了一个map,然后在析构函数里析构就好了。(这里的优化在下面的封装Connection里)

TcpServer::~TcpServer() {
    delete m_acpt;
    for (auto& one : m_con_mp) {
        delete one.second;
    }
}

这里在放上Acceptor类的代码:

#include "Acceptor.h"
#include "Socket.h"
#include "InetAddress.h"
#include "Channel.h"
#include "EventLoop.h"
#include "Connection.h"

Acceptor::Acceptor(EventLoop* el, const std::string& ip, const uint16_t& port) :m_el(el) {
    m_listen = new Socket(CreatNonBlocking());
    m_listen->KeepAlive(1);
    m_listen->NoDelay(1);
    m_listen->ReuseAddr(1);
    m_listen->ReusePort(1);

    InetAddress serv_addr(ip, port);
    m_listen->Bind(serv_addr);
    m_listen->Listen(128);

    m_ch = new Channel(m_el->GetEP(), m_listen->GetFD());

    m_ch->EnableReading();
    m_ch->SetReadCallback(std::bind(&Acceptor::NewConnection, this));

    m_el->Update(m_ch);
}

Acceptor::~Acceptor() {
    delete m_listen;
    delete m_ch;
}

void Acceptor::SetNewConnection(std::function<void(Socket*)> func) {
    m_new_connection = func;
}

void Acceptor::NewConnection() {
    InetAddress client_addr1;
    Socket* client = new Socket(m_listen->Accept(client_addr1));
    client->SetAddr(client_addr1);

    m_new_connection(client);
}

封装Connection类:

在实现了Acceptor类之后,其实Connection也是大同小异,我们将之前Channel类的OnMessage函数挪过来就好了,这里放上函数的具体实现:

#include "Connection.h"
#include "Channel.h"
#include "Socket.h"
#include "EventLoop.h"
Connection::Connection(EventLoop* el, Socket* client) :m_el(el), m_client(client) {
    m_ch = new Channel(m_el->GetEP(), client->GetFD());
    m_ch->EnableET();
    m_ch->EnableReading();
    m_ch->SetReadCallback(std::bind(&Connection::OnMessage, this));
    m_el->Update(m_ch);
}

Connection::~Connection() {
    delete m_ch;
    delete m_client;
}

void Connection::OnMessage() {
    char buf[1024];
    while (true) {

        memset(buf, 0, sizeof buf);

        ssize_t read_siz = read(m_client->GetFD(), buf, sizeof buf);

        if (read_siz > 0) {
            printf("recv: (fd: %d) %s\n", m_client->GetFD(), buf);
            send(m_client->GetFD(), buf, sizeof buf, 0);
        }
        else if (read_siz == -1 and errno == EINTR) {
            continue;
        }
        else if (read_siz == -1 and (errno == EAGAIN || errno == EWOULDBLOCK)) {
            break;
        }
        else if (read_siz == 0) {
            printf("disconnect: (fd: %d) \n", m_client->GetFD());
            close(m_client->GetFD());
            break;
        }
    }
}

关于析构Connection:

我们可以想到,像上面提到的用map的析构方法并不太好,因为我们希望客户端断开之后就可以析构,而不是直到最后才析构。

这里我们先优化一下前面的内容:

关于Channel的Handle函数:

void Channel::HandleEvent() {
    if (this->GetRevent() & EPOLLIN) {
        read_callback();
    }
    else if (this->GetRevent() & EPOLLOUT) {
    }
    else {
        error_callback();
        // printf("error: (fd: %d) \n", this->GetFD());
        // close(this->GetFD());
    }
}

这里我们用回调函数,就像之前用read_callback一样,不再使用注释的内容了,而这样做,我们就需要像下面这样:

    std::function<void()> read_callback;
    std::function<void()> close_callback;
    std::function<void()> error_callback;

    void SetReadCallback(std::function<void()> func);
    void SetCloseCallback(std::function<void()> func);
    void SetErrorCallback(std::function<void()> func);

多设置出来两个function和两个setfunction的函数。

而set函数的调用,我们就需要到Connection的构造函数里:

Connection::Connection(EventLoop* el, Socket* client) :m_el(el), m_client(client) {
    m_ch = new Channel(m_el->GetEP(), client->GetFD());
    m_ch->EnableET();
    m_ch->EnableReading();
    m_ch->SetReadCallback(std::bind(&Connection::OnMessage, this));
    m_ch->SetCloseCallback(std::bind(&Connection::CloseCallback, this));
    m_ch->SetErrorCallback(std::bind(&Connection::ErrorCallback, this));
    m_el->Update(m_ch);
}

可以看到中间多了两个刚才写的SetCallback函数的调用,那内容该怎么写呢?

我们要记得,Connection想要被delete掉,要在TcpServer里才可以。

所以我们有两个解决方法,一个是在Connection类里再加上TcpServer指针,这样在setclosecallback函数的里面就可以填上TcpServer的指针(如果没有TcpServer的指针,我们就无法在TcpServer里delete。

第二个方法就是就像刚才做的一样,我们在Connection里也用上回调函数,把刚才做的套娃一遍,这样在没有TcpServer的指针的情况下也可以做到在TcpServer里delete了,这里我使用的第二个方法解决。


class Connection {
private:
    EventLoop* m_el;
    Channel* m_ch;
    Socket* m_client;
    std::string m_ip;
    uint16_t m_port;

    std::function<void(Connection*)> m_close_callback;
    std::function<void(Connection*)> m_error_callback;

public:
    Connection(EventLoop* el, Socket* client);
    ~Connection();

    void OnMessage();

    void SetCloseCallback(std::function<void(Connection*)> func);
    void SetErrorCallback(std::function<void(Connection*)> func);
    void CloseCallback();
    void ErrorCallback();
};

这里因为我们要delete Connection,所以回调函数有参数,是Connection的指针类型。

然后我们就接着回到TcpServer里new Connection的函数部分:

void TcpServer::NewConnection(Socket* client) {
    Connection* m_conn = new Connection(&m_el, client);
    m_conn->SetCloseCallback(std::bind(&TcpServer::CloseConnection, this, std::placeholders::_1));
    m_conn->SetErrorCallback(std::bind(&TcpServer::ErrorConnection, this, std::placeholders::_1));
    printf("New Client fd:%d  ip:%s  port:%hu \n", client->GetFD(), client->GetIP().c_str(), client->GetPort());
}
using std::cout;
using std::endl;
void TcpServer::CloseConnection(Connection* con) {
    delete con;
    cout << "CLoseCon" << endl;
}

void TcpServer::ErrorConnection(Connection* con) {
    delete con;
    cout << "ErrorCon" << endl;
}

可以看到,我们也加上了SetCloseCallback的部分,因为要有Connection*的部分,所以用placeholders先暂时代替。

运行代码后,会看到有CloseCon的内容,代表我们确实使用回调函数delete的。

最后附上Connection.cpp的内容:

#include "Connection.h"
#include "Channel.h"
#include "Socket.h"
#include "EventLoop.h"
#include "TcpServer.h"

Connection::Connection(EventLoop* el, Socket* client) :m_el(el), m_client(client) {
    m_ch = new Channel(m_el->GetEP(), client->GetFD());
    m_ch->EnableET();
    m_ch->EnableReading();
    m_ch->SetReadCallback(std::bind(&Connection::OnMessage, this));
    m_ch->SetCloseCallback(std::bind(&Connection::CloseCallback, this));
    m_ch->SetErrorCallback(std::bind(&Connection::ErrorCallback, this));

    m_el->Update(m_ch);
}

Connection::~Connection() {
    delete m_ch;
    delete m_client;
}

void Connection::OnMessage() {
    char buf[1024];
    while (true) {

        memset(buf, 0, sizeof buf);

        ssize_t read_siz = read(m_client->GetFD(), buf, sizeof buf);

        if (read_siz > 0) {
            printf("recv: (fd: %d) %s\n", m_client->GetFD(), buf);
            send(m_client->GetFD(), buf, sizeof buf, 0);
        }
        else if (read_siz == -1 and errno == EINTR) {
            continue;
        }
        else if (read_siz == -1 and (errno == EAGAIN || errno == EWOULDBLOCK)) {
            break;
        }
        else if (read_siz == 0) {
            printf("disconnect: (fd: %d) \n", m_client->GetFD());
            CloseCallback();
            // m_close_callback(this);
            // close(m_client->GetFD());
            // break;
        }
    }
}

void Connection::SetCloseCallback(std::function<void(Connection*)> func) {
    m_close_callback = func;
}

void Connection::SetErrorCallback(std::function<void(Connection*)> func) {
    m_error_callback = func;
}

void Connection::CloseCallback() {
    m_close_callback(this);
}

void Connection::ErrorCallback() {
    m_error_callback(this);
}

所以最后我们总结一下:

设B类是A类的下层级,当我们想要A的函数来进行操作B的时候,我们就给B的成员加上function,还有加上SetFunction函数去设置回调函数。然后就找到A中new B的部分,补上SetFunction的内容,将其想要的函数内容设置成A类有的函数。这样就能做到在A中实现某些事情。例如如果我们想要在A中delete B的话,就可以使用以上内容。

Others:

把输出连接的信息挪到了TcpServer里,所以我在Acceptor和Connection里都多加了ip和port变量。

posted @   AdviseDY  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示