系列文章目录: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、总结
在这一节中,解决了上一节中的二次析构问题,但这仍然是应答式服务器。在下一节中,会将这个服务器改为全双工通信方式,这也是企业中常用的模式。