前言
前面的异步服务器为echo模式,但其存在安全隐患,就是在极端情况下客户端关闭导致触发写和读回调函数,二者都进入错误处理逻辑,进而造成二次析构的问题。下面我们将通过C11智能指针构造一个伪闭包的状态延长session的生命周期。(伪闭包不是真正的闭包,而是通过智能指针和函数绑定的组合来模拟闭包的行为,以确保对象在其仍然被使用的回调函数中保持有效。)
一、智能指针管理Session
我们可以通过智能指针的方式管理Session类,将acceptor接收的链接保存在Session类型的智能指针里.
void Server::start_accept()
{
shared_ptr<Session> new_session = make_shared<Session>(_ios,this);//智能指针
//Session* new_session = new Session(_ios);//以前用new写的
_acceptor.async_accept(new_session->Socket(), std::bind(&Server::handle_accept, this, new_session, std::placeholders::_1));
}
void handle_accept(shared_ptr<Session> new_session, const boost::system::error_code& error);
Start_accept函数中虽然new_session是一个局部变量,但是我们通过bind操作,将new_session作为数值传递给bind函数,而bind函数返回的函数对象内部引用了该new_session所以引用计数增加1,这样保证了new_session不会被释放。但是当智能指针的引用计数为0时会自动析构,所以为了防止其被自动回收,也方便Server管理Session,我们在Server类中添加map类型成员变量,key为Session的uid,value为该Session的智能指针。(session的uuid可以通过boost提供的生成唯一id的函数获得)。
//map类型的_sessions
std::map <std::string,shared_ptr<Session>> _sessions;
1.Session的uuid
关于session的uuid可以通过boost提供的生成唯一id的函数获得.
public:
//构造函数
Session(boost::asio::io_context& ios,Server* server) :_socket(ios),_server(server)
{
//生成uuid
boost::uuids::uuid a_uuid = boost::uuids::random_generator()();
//保存uuid
_uuid = boost::uuids::to_string(a_uuid);
}
//一个获取uuid函数,方便后面Server中使用
std::string& Session::GetUuid()
{
return _uuid;
}
2.读入数据
通过Server中的_sessions这个map管理链接,可以增加Session智能指针的引用计数,只有当Session从这个map中移除后,Session才会被释放。
所以在接收连接的逻辑里将Session放入map
void Server::start_accept()
{
shared_ptr<Session> new_session = make_shared<Session>(_ios,this);
//Session* new_session = new Session(_ios);
_acceptor.async_accept(new_session->Socket(), std::bind(&Server::handle_accept, this, new_session, std::placeholders::_1));
}
void Server::handle_accept(shared_ptr<Session> new_session, const boost::system::error_code& error)
{
if (!error)
{
//成功
new_session->Start();
//Server管理session
_sessions.insert(make_pair(new_session->GetUuid(), new_session));
}
else
{
//delete new_session;
//没连接成功
}
start_accept();
}
再写一个释放函数,将session从map中移除,当其引用计数为0则自动释放
void Server::clearSession(std::string uuid)
{
_sessions.erase(uuid);
}
我们在服务器要发送数据前打个断点,此时关闭客户端,在服务器就会先触发写回调函数的错误处理,再触发读回调函数的错误处理,这样session就会两次从map中移除,因为map中key唯一,所以第二次map判断没有session的key就不做移除操作了。但是这么做还是会有崩溃问题,因为第一次在session写回调函数中移除session,session的引用计数就为0了,调用了session的析构函数,这样在触发session读回调函数时此时session的内存已经被回收了自然会出现崩溃的问题。解决这个问题可以利用智能指针引用计数和bind的特性,实现一个伪闭包的机制延长session的生命周期。
二、构造伪闭包
bind操作可以将值绑定在一个函数对象上生成新的函数对象,如果将智能指针作为参数绑定给函数对象,那么智能指针就以值的方式被新函数对象使用,那么智能指针的生命周期将和新生成的函数对象一致,从而达到延长生命的效果。
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred,shared_ptr<Session> _self_shared);//接收客户端发来数据时用
void handle_write(const boost::system::error_code& error,
shared_ptr<Session> _self_shared);
1.handle_read
void Session::handle_read(const boost::system::error_code& error, size_t bytes_transferred,
shared_ptr<Session> _self_shared)
{
if (error)
{
cout << "read error" << endl;
//delete this;
_server->clearSession(_uuid);
}
else
{
cout << "server receive data is:"<<_data<<endl ;
boost::asio::async_write(_socket, boost::asio::buffer(_data, bytes_transferred),
std::bind(&Session::handle_write, this, std::placeholders::_1,_self_shared));
//这里只传一个参是因为handle_write回调函数只有一个参数
}
}
2.handle_write
void Session::handle_write(const boost::system::error_code& error, shared_ptr<Session> _self_shared)
{
if (error)
{
cout << "write error" << endl;
//delete this;
_server->clearSession(_uuid);
}
else
{
memset(_data, 0, max_length);
_socket.async_read_some(boost::asio::buffer(_data, max_length),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2, _self_shared));
}
}
除此之外,我们也要在第一次绑定读写回调函数的时候传入智能指针的值,但是要注意传入的方式,不能用两个智能指针管理同一块内存,如下用法是错误的。
void Session::Start(){
memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&Session::HandleRead, this,
std::placeholders::_1, std::placeholders::_2, shared_ptr<Session>(this)));
}
shared_ptr(this)生成的新智能指针和this之前绑定的智能指针并不共享引用计数,所以要通过shared_from_this()函数返回智能指针,该智能指针和其他管理这块内存的智能指针共享引用计数。
void Session::Start()
{
memset(_data, 0, max_length);
_socket.async_read_some(boost::asio::buffer(_data, max_length),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2,shared_from_this()));
}
shared_from_this()函数并不是session的成员函数,要使用这个函数需要继承std::enable_shared_from_this。
class Session:public std::enable_shared_from_this<Session>
这样就不会出现二次析构的问题了。
总结
我们通过bind和智能指针实现伪闭包,保证在回调函数出发前session都是有效存活的。