项目做完上线,发现存在内存泄漏。因为客户端链接到服务器时传统new出来对象,断开链接后没有进行释放。
代码如下:
//1、开启服务监听
bool CWebServer::StartServer(const std::string& strIp, uint16 nPort)
{
try{
boost::system::error_code ec;
#if BOOST_VERSION >= 106600
boost::asio::ip::address addr = boost::asio::ip::make_address(strIp, ec);
#else
boost::asio::ip::address addr = boost::asio::ip::address::from_string(strIp, ec);
#endif
if(ec)
{
std::cout << "error message:" << ec.message() << std::endl;
}
boost::asio::ip::tcp::endpoint endpoint(addr,nPort);
m_strIP = strIp;
m_usPort = nPort;
m_acceptor = new boost::asio::ip::tcp::acceptor(sIOService->GetIOService(),endpoint,true);
//2019/04/29 add
m_acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
}catch(boost::system::error_code ec)
{
TC_LOG_ERROR("apilib.log", "%s message:%s", __FUNCTION__, ec.message().c_str());
}
return true;
}
//2、new 出来的CWebClient对象没有释放
CWebClient* client = new CWebClient(get_io_context(m_acceptor));
m_acceptor->async_accept(client->get_socket(), boost::bind(&CWebServer::accept_handler, this, client, boost::asio::placeholders::error));
//3、add_client(client); 加入链接对象
void CWebServer::accept_handler(CWebClient* client, const boost::system::error_code &ec)
{
if (ec)
{
client->conn_error_handler(ec);
start();
return;
}
try
{
start();
boost::asio::ip::tcp::endpoint remote = client->get_socket().remote_endpoint();
client->SetIP(remote.address().to_string());
client->init(m_pOnEnter, m_pOnMsg, m_pOnExit);
client->start();
add_client(client);
}
catch (boost::system::error_code ec)
{
TC_LOG_ERROR("apilib.log", "%s message:%s", __FUNCTION__, ec.message().c_str());
}
}
//4、保存客户端连接socket
void CWebServer::add_client(CWebClient* client)
{
m_mapClient.insert(std::make_pair(client->GetHandleID(), client));
}
然后发现当客户端链接断开的时候,没有地方释放new出来的对象(水平有限没有使用对象池,频繁new delete对象不好,后期学习进而优化)。
修改代码如下:
//1、添加async_wait定时回调函数update
bool CWebServer::StartServer(const std::string& strIp, uint16 nPort)
{
try{
boost::system::error_code ec;
#if BOOST_VERSION >= 106600
boost::asio::ip::address addr = boost::asio::ip::make_address(strIp, ec);
#else
boost::asio::ip::address addr = boost::asio::ip::address::from_string(strIp, ec);
#endif
if(ec)
{
std::cout << "error message:" << ec.message() << std::endl;
}
boost::asio::ip::tcp::endpoint endpoint(addr,nPort);
m_strIP = strIp;
m_usPort = nPort;
m_acceptor = new boost::asio::ip::tcp::acceptor(sIOService->GetIOService(),endpoint,true);
//2019/04/29 add
m_acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
//2019/05/09 add
m_updateTimer.expires_from_now(boost::posix_time::milliseconds(1000));
m_updateTimer.async_wait(std::bind(&CWebServer::update, this));
}catch(boost::system::error_code ec)
{
TC_LOG_ERROR("apilib.log", "%s message:%s", __FUNCTION__, ec.message().c_str());
}
return true;
}
//2、不断检测连接是否断开 delete释放对象
void CWebServer::update()
{
m_updateTimer.expires_from_now(boost::posix_time::milliseconds(1000));
m_updateTimer.async_wait(std::bind(&CWebServer::update, this));
for (std::map::iterator it = m_mapClient.begin(); it != m_mapClient.end(); )
{
if (it->second->CloseStatus())
{
delete(it->second);
m_mapClient.erase(it++);
}
else
{
++it;
}
}
}
说明:这样就能够保持正常new delete对象。
//1、客户端链接对象继承enable_shared_from_this模板类
class CWebClient : public std::enable_shared_from_this{...};
//2、使用make_shared分配内存对象,一次分配防止出错
void CWebServer::start()
{
#if TC_SOCKET_TYPE == TC_SOCKET_SHARED_PTR
std::shared_ptr client = std::make_shared(get_io_context(m_acceptor));
m_acceptor->async_accept(client->get_socket(), boost::bind(&CWebServer::accept_handler, this, client, boost::asio::placeholders::error));
#else
CWebClient* client = new CWebClient(get_io_context(m_acceptor));
m_acceptor->async_accept(client->get_socket(), boost::bind(&CWebServer::accept_handler, this, client, boost::asio::placeholders::error));
#endif
}
//3、异步接受改写
#if TC_SOCKET_TYPE == TC_SOCKET_SHARED_PTR
void CWebServer::accept_handler(std::shared_ptr client, const boost::system::error_code &ec)
#else
void CWebServer::accept_handler(CWebClient* client, const boost::system::error_code &ec)
#endif
{
if (ec)
{
client->conn_error_handler(ec);
start();
return;
}
try
{
start();
boost::asio::ip::tcp::endpoint remote = client->get_socket().remote_endpoint();
client->SetIP(remote.address().to_string());
client->init(m_pOnEnter, m_pOnMsg, m_pOnExit);
client->start();
add_client(client);
}
catch (boost::system::error_code ec)
{
TC_LOG_ERROR("apilib.log", "%s message:%s", __FUNCTION__, ec.message().c_str());
}
}
//4、server链接存储改写
#if TC_SOCKET_TYPE == TC_SOCKET_SHARED_PTR
void CWebServer::add_client(std::shared_ptr client)
#else
void CWebServer::add_client(CWebClient* client)
#endif
{
m_mapClient.insert(std::make_pair(client->GetHandleID(), client));
}
//5、检测释放改写
void CWebServer::update()
{
m_updateTimer.expires_from_now(boost::posix_time::milliseconds(1000));
m_updateTimer.async_wait(std::bind(&CWebServer::update, this));
#if TC_SOCKET_TYPE == TC_SOCKET_SHARED_PTR
for (std::map>::iterator it = m_mapClient.begin(); it != m_mapClient.end(); )
#else
for (std::map::iterator it = m_mapClient.begin(); it != m_mapClient.end(); )
#endif
{
if (it->second->CloseStatus())
{
#if TC_SOCKET_TYPE == TC_SOCKET_NORMAL_PTR
delete(it->second);
#endif
m_mapClient.erase(it++);
}
else
{
++it;
}
}
}
说明:
1、继承enable_shared_from_this
2、delete(it->second)就不需要调用了,在m_mapClient.erase的时候,对象内存就会自动释放,这就是智能指针的好处。
3、如何判断对象是否释放。
1)先去获取对象,日志输出为0,等它释放后,调用expired()函数,日志输出为1,就表示成功释放。
2)我们换个方式,调试看看效果。
看见没,释放前,1个强引用shared_ptr,2个弱引用weak_ptr。 我们继续下一步。
释放后,只有1个弱引用,就是当前pWeak引用。其实这里就应经知道对象释放了,再继续下一步。
pWeak调用lock()获取shared_ptr对象为empty。进一步表明对象不存在,已经自动被释放。
注:1、测试说明std::cout,项目中还是严谨些好。2、智能指针,代码有误,请指正,谢谢。