本系列主要记录自己在学习过程的思路、问题、解决方法。最后实现一个高性能的HTTP 服务器,用于学习cpp、网络编程、服务器相关的内容。
系列目录
随着项目的不断推进,已经基本实现了一个基于reactor模型的高并发服务器的雏型,为了能够更好地完成接下来的内容,同时弥补之前的设计、细节缺陷,需要对核心类与代码进行梳理和重构。主要是使用智能指针对类资源进行管理,避免可能出现的内存泄漏等情况,提高服务器的基础效率。
本文目录
一、梳理资源的生命周期
为了能够更好地使用智能指针管理各个类中的成员,我们需要对所有类中的成员关系、类间的所有关系进行一个整理,这里使用sourcetrail对项目进行分析来辅助我们梳理资源之间的关系。
具体操作详见往期文章:Sourcetrail使用说明(CPP项目)
| 资源 | 创建时机 | 创建(拥有)者 | 使用者 |
|---|---|---|---|
| 监听Socket | Acceptor初始化 | Acceptor | Acceptor、channel |
| 客户端socket | 新连接建立时 | connection | connection、channel |
| 监听Channel | Acceptor初始化 | Acceptor | Acceptor-loop-epoll |
| 客户端Channel | connection初始化 | connection | Connection-loop-epoll |
| Connection | 新连接建立时 | 服务器 | 服务器 |
| Acceptor | 服务器初始化 | 服务器 | 服务器 |
| Buffer | Connection初始化 | Connection | Connection |
| Threadpool | 服务器初始化 | 服务器 | 服务器 |
| 资源 | 释放时机 | 释放者 |
|---|---|---|
| 监听Socket | 服务器关闭 | Acceptor |
| 客户端socket | connection关闭 | connection |
| 监听Channel | 服务器关闭 | Acceptor |
| 客户端Channel | connection关闭 | connection |
| Connection | 收到关闭信号 | 服务器 |
| Acceptor | 服务器关闭 | 服务器 |
| Buffer | connection关闭 | 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 的底层资源管理变得更加清晰、健壮。接下来,可以以此为模板,逐步推广到其他核心类,进一步统一生命周期管理方式,使整个服务器框架的资源管理更加规范和可维护。
858

被折叠的 条评论
为什么被折叠?



