六、模拟伪闭包实现连接的安全回收

        系列文章目录:C++ asio网络编程-CSDN博客 

        之前的异步服务器存在安全隐患,就是在极端情况下客户端关闭导致触发写和读回调函数,二者都进入错误处理逻辑,进而造成二次析构的问题。下面我们介绍通过C11智能指针构造成一个伪闭包的状态延长session的生命周期。

1、智能指针管理Session

        为了方便管理session,我们可以使用智能指针,将acceptor到的连接保存在一个管理session的容器,这里我们可以使用map容器,key为uid,value为session的智能指针。引入uid的原因是后面要实现重连踢人等业务逻辑,所以要使用uid区分不同的连接。

// 要使用uuid,加上这两个头文件
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>

Session::Session(boost::asio::io_context& ioc, Server* server)
	: _socket(ioc), _server(server)
{
	// 生成唯一id,可以了解一下雪花算法
	// 这里直接使用boost自带的方法
	boost::uuids::uuid a_uuid = boost::uuids::random_generator()();
	_uuid = boost::uuids::to_string(a_uuid);

}

std::string& Session::getUuid()
{
	return _uuid;
}

        在server中定义map管理连接

class Server
{
public:
	Server(boost::asio::io_context& ioc, unsigned short port);
	void clearSession(std::string uuid);

private:
	void start_accept();
	void handle_accept(std::shared_ptr<Session> new_session, const boost::system::error_code& ec);

	boost::asio::io_context& _ioc;  // io_context不允许被复制
	boost::asio::ip::tcp::acceptor _acceptor;
	std::map<std::string, std::shared_ptr<Session>> _sessions;
};
void Server::start_accept()
{
	std::shared_ptr<Session> new_session = std::make_shared<Session>(_ioc, this);

	_acceptor.async_accept(new_session->getSocket(),
		std::bind(&Server::handle_accept, this, new_session, std::placeholders::_1));
}

void Server::handle_accept(std::shared_ptr<Session> new_session, const boost::system::error_code& ec)
{
	if (!ec) {
		new_session->start();
		_sessions.insert(std::make_pair(new_session->getUuid(), new_session));
	}
	else {
		// delete new_session;
	}

	start_accept();
}

void Server::clearSession(std::string uuid)
{
	_sessions.erase(uuid);
}

2、构造伪闭包

实现思路:

  • 利用智能指针被复制或使用引用计数加一的原理保证内存不被回收
  • bind操作可以将值绑定在一个函数对象上生成新的函数对象,如果将智能指针作为参数绑定给函数对象,那么智能指针就以值的方式被新函数对象使用,那么智能指针的生命周期将和新生成的函数对象一致,从而达到延长生命的效果。

        基于这个思路,改写回调函数:

	void handle_read(const boost::system::error_code& ec,
		std::size_t bytes_transferred,
		std::shared_ptr<Session> self_share);
	void handle_write(const boost::system::error_code& ec, 
		std::shared_ptr<Session> self_share);

        在bind时传递self_shared指针增加其引用计数,这样_self_shared的生命周期就和回调函数对象的生命周期一致了。

void Session::handle_read(const boost::system::error_code& ec, 
	std::size_t bytes_transferred, std::shared_ptr<Session> self_share)
{
	if (!ec) {
		std::cout << "server receive data is " << _data << std::endl;
		send(_data, bytes_transferred);
		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_share));
	}
	else {
		std::cout << "read error" << std::endl;
		// delete this;
		_server->clearSession(_uuid);
	}
}

void Session::handle_write(const boost::system::error_code& ec, 
	std::shared_ptr<Session> self_share)
{
	if (!ec) {
		std::lock_guard<std::mutex> lock(_send_lock);
		_send_que.pop();
		if (!_send_que.empty()) {
			auto& msgNode = _send_que.front();
			boost::asio::async_write(_socket, boost::asio::buffer(msgNode->_data, msgNode->_max_len),
				std::bind(&Session::handle_write, this, std::placeholders::_1, self_share));
		}
	}
	else {
		std::cout << "write error: " << ec.value() << std::endl;
		// delete this;
		_server->clearSession(_uuid);
	}
}

        此外,我们要注意第一次绑定读写函数时智能指针的传入方式,要使用shared_from_this()函数返回智能指针,该智能指针和其他管理这块内存的智能指针共享引用计数。要使用shared_from_this函数,需要继承std::enable_shared_from_this。

class Session : public std::enable_shared_from_this<Session>
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())
	);
}

        到此,就解决了二次析构的问题。

3、总结

        在这一节中,解决了上一节中的二次析构问题,但这仍然是应答式服务器。在下一节中,会将这个服务器改为全双工通信方式,这也是企业中常用的模式。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值