基于C++的HTTP WebServer实现(3)--核心代码重构与优化(1)

 本系列主要记录自己在学习过程的思路、问题、解决方法。最后实现一个高性能的HTTP 服务器,用于学习cpp、网络编程、服务器相关的内容。


系列目录

  1. HTTPServer-动态缓冲区
  2. HTTPServer-Connection类逻辑优化
  3. git相关问题与解决方法
  4. Sourcetrail使用说明(CPP项目)

随着项目的不断推进,已经基本实现了一个基于reactor模型的高并发服务器的雏型,为了能够更好地完成接下来的内容,同时弥补之前的设计、细节缺陷,需要对核心类与代码进行梳理和重构。主要是使用智能指针对类资源进行管理,避免可能出现的内存泄漏等情况,提高服务器的基础效率。

本文目录

一、梳理资源的生命周期

二、重构MySocket类相关问题

1.MyConnection类直接管理MySocket对象

2.MySocket调用链条更新重构

(1)重构MySocket构造函数和acceptConn函数

(2)修改 Acceptor 回调类型

(3)重构MyServer中的相关调用

3. 最后层级

三、总结

        

一、梳理资源的生命周期

为了能够更好地使用智能指针管理各个类中的成员,我们需要对所有类中的成员关系、类间的所有关系进行一个整理,这里使用sourcetrail对项目进行分析来辅助我们梳理资源之间的关系。

具体操作详见往期文章:Sourcetrail使用说明(CPP项目)

资源所有权
资源创建时机创建(拥有)者使用者
监听SocketAcceptor初始化AcceptorAcceptor、channel
客户端socket新连接建立时connectionconnection、channel
监听ChannelAcceptor初始化AcceptorAcceptor-loop-epoll
客户端Channelconnection初始化connectionConnection-loop-epoll
Connection新连接建立时服务器服务器
Acceptor服务器初始化服务器服务器
BufferConnection初始化ConnectionConnection
Threadpool服务器初始化服务器服务器
资源释放时机释放者
监听Socket服务器关闭Acceptor
客户端socketconnection关闭connection
监听Channel服务器关闭Acceptor
客户端Channelconnection关闭connection
Connection收到关闭信号服务器
Acceptor服务器关闭服务器
Bufferconnection关闭connection
Threadpool服务器关闭服务器

这是生成的大致图像,在分析过程中还需要具体对某个类进行设置检索:

二、重构MySocket类

1.MyConnection类直接管理MySocket对象

在最初版本中,MyConnection中使用fd裸指针作为成员函数,其逻辑就会散落在 Connection 或调用者中,耦合度高,不利于维护。而使用 MySocket 作为封装,可以带来明显好处:

  • RAII 管理:自动在析构时 close(fd),避免忘记关闭。

  • 统一接口:把 send/recv/connect/bind 包装成成员函数,避免在各处散落调用裸 API。

修改后的构造函数和类定义:

MyConnection::MyConnection(MyEventLoop *loop, MySocket&& client_socket) : event_loop_(loop), inputBuffer_(), outputBuffer_(), connection_callback_(nullptr)
{
    client_socket_ = std::move(client_socket);
}

class MyConnection
{
public:
    MyConnection(MyEventLoop *loop, MySocket&& client_socket);
    ~MyConnection();

private:
    MySocket client_socket_;
    MyEventLoop *event_loop_;
    std::function<void(int)> connection_callback_;
    MyChannel *channel_;
    MyBuffer inputBuffer_;
    MyBuffer outputBuffer_;
};

这里的 MySocket&& socket右值引用

  • 期望调用者把一个临时的 MySocket 传进来。

  • 在初始化列表里 std::move(socket),把这个临时对象的资源(socket)转移给成员变量 client_socket_

2.MySocket调用链条更新重构

在最初的版本中,connection的构建过程中传递的一直是socketfd,需要重构整个链条的参数传递来适应对于MySocket对象的管理。

(1)重构MySocket构造函数和acceptConn函数

在初版中acceptConn只返回一个fd标识符,通过fd的传递来构造connection,重构后使其可以返回一个MySocket对象,并传递这个对象。为此还需要重载一个构造函数,使其能够通过传入fd来构造一个对象:

explicit MySocket::MySocket(int fd) : sockfd_(fd) , addr_{} {}

MySocket MySocket::acceptConn()
{
    sockaddr_in c_addr{};
    socklen_t c_addr_len = sizeof(c_addr);
    int cfd = accept4(sockfd_, (sockaddr *)&c_addr, &c_addr_len,
                      SOCK_NONBLOCK | SOCK_CLOEXEC);
    if (cfd < 0)
    {
        perror("accept failed");
    }
    return MySocket(cfd);
}

为什么要使用explict:

防止隐式转换,如果一个类有 单参数构造函数,编译器会默认把它当作 类型转换构造函数

(2)修改 Acceptor 回调类型

// 原来是 std::function<void(int)>
using AcceptorCallback = std::function<void(MySocket)>;
void setAcceptorCallBack(AcceptorCallback cb) 
{
    acceptor_callback_ = std::move(cb);
}

(3)重构MyServer中的相关调用

void MyServer::handleServerEvent(MySocket clientSock) 
{
    newConnection(std::move(clientSock));
}

void MyServer::newConnection(MySocket clientSock) 
{
    MyEventLoop* ioLoop = thread_pool_->getNextLoop();
    auto conn = std::make_shared<MyConnection>(ioLoop, std::move(clientSock));
    // 保存 conn,避免提前析构
    connections_[conn->fd()] = conn;
}

acceptor_->setAcceptorCallBack(
    [this](MySocket clientSock) {
        this->handleServerEvent(std::move(clientSock));
    }
);

3. 最后层级

可见重构后的层级非常清晰:

三、总结

在这一阶段的重构中,我们首先通过 Sourcetrail 梳理了项目中各类资源的生命周期,明确了所有权和释放时机;随后以 MySocket 与 Connection 为突破口,将原本依赖裸 fd 的实现改为由 RAII 封装类接管,从而保证了 socket 在多层回调链路中的安全传递,减少了资源泄漏和管理分散的问题。通过这一改造,HTTPServer 的底层资源管理变得更加清晰、健壮。接下来,可以以此为模板,逐步推广到其他核心类,进一步统一生命周期管理方式,使整个服务器框架的资源管理更加规范和可维护。

基于开源项目《30daysCppWebServer》进行学习、实现、优化

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值