伪闭包延长生命周期


前言

前面的异步服务器为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都是有效存活的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值