我已经创建了一个从客户端读取数据的服务器,我使用boost :: asio async_read_some来读取数据,我已经创建了一个处理函数,这里_ioService-> poll()将运行事件处理循环来执行就绪处理程序。在handler _handleAsyncReceive中,我正在释放在receiveDataAsync中分配的buf。 bufferSize是500。 代码如下:
bool
TCPSocket::receiveDataAsync( unsigned int bufferSize )
{
char *buf = new char[bufferSize + 1];
try
{
_tcpSocket->async_read_some( boost::asio::buffer( (void*)buf, bufferSize ),
boost::bind(&TCPSocket::_handleAsyncReceive,
this,
buf,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred) );
_ioService->poll();
}
catch (std::exception& e)
{
LOG_ERROR("Error Receiving Data Asynchronously");
LOG_ERROR( e.what() );
delete [] buf;
return false;
}
//we dont delete buf here as it will be deleted by callback _handleAsyncReceive
return true;
}
void
TCPSocket::_handleAsyncReceive(char *buf, const boost::system::error_code& ec, size_t size)
{
if(ec)
{
LOG_ERROR ("Error occurred while sending data Asynchronously.");
LOG_ERROR ( ec.message() );
}
else if ( size > 0 )
{
buf[size] = '\0';
LOG_DEBUG("Deleting Buffer");
emit _asyncDataReceivedSignal( QString::fromLocal8Bit( buf ) );
}
delete [] buf;
}
这里的问题是,与释放相比,缓冲区的分配速度要快得多,因此内存使用率将以指数速率变高,并且在某些时间点它会占用所有内存并且系统将被卡住。 CPU使用率也将在90%左右。如何减少内存和CPU消耗?
答案 0 :(得分:2)
你有内存泄漏。 io_service民意调查并不能保证发送您的_handleAsyncReceive
。它可以发送其他事件(例如接受),因此char *buf
的内存丢失。我猜你是从一个循环中调用receiveDataAsync
,但它没有必要 - 在任何情况下都会存在泄漏(泄漏速度不同)。
如果您按照asio examples并使用建议的模式而不是制作自己的模式,效果会更好。
答案 1 :(得分:2)
您可以考虑使用环绕缓冲区,也称为循环缓冲区。 Boost有一个模板循环缓冲版本可用。你可以阅读它here.它背后的想法是,当它变满时,它会绕到它将存储东西的开头。您也可以对其他结构或数组执行相同的操作。例如,我目前在我的应用程序中为此目的使用了一个字节数组。
使用专用的大型循环缓冲区来保存消息的好处是,您不必担心为每个新消息创建和删除内存。这可以避免内存碎片,这可能会成为一个问题。
要确定循环缓冲区的适当大小,您需要考虑可以进入并处于同时处理的某个阶段的最大消息数;将该数字乘以消息的平均大小,然后乘以大约1.5的软糖因子。我的应用程序的平均邮件大小不到100个字节。我的缓冲区大小为1兆字节,这将允许累积至少10,000条消息,而不会影响环绕缓冲区。但是,如果超过10,000条消息未经完全处理而累积,则循环缓冲区将无法使用,程序将不得不重新启动。我一直在考虑减小缓冲区的大小,因为系统在达到10,000个消息标记之前可能已经很久了。
答案 2 :(得分:2)
正如PSIAlt建议的那样,请考虑遵循Boost.Asio examples并构建其异步编程模式。
尽管如此,我建议考虑是否需要将多个读取调用排队到同一个套接字上。如果应用程序仅允许单个读取操作在套接字上挂起,则资源会减少:
io_service
中不再存在待处理的处理程序过多的情况。可以预先分配单个缓冲区并重复用于每个读取操作。例如,以下异步调用链仅需要单个缓冲区,并且允许在Qt信号上发出先前数据时同时执行启动异步读取操作,因为QString
执行深层复制。 / p>
TCPSocket::start()
{
receiveDataAsync(...) --.
} |
.---------------'
| .-----------------------------------.
v v |
TCPSocket::receiveDataAsync(...) |
{ |
_tcpSocket->async_read_some(_buffer); --. |
} | |
.-------------------------------' |
v |
TCPSocket::_handleAsyncReceive(...) |
{ |
QString data = QString::fromLocal8Bit(_buffer); |
receiveDataAsync(...); --------------------------'
emit _asyncDataReceivedSignal(data);
}
...
tcp_socket.start();
io_service.run();
确定io_service
事件循环的服务时间和位置非常重要。通常,应用程序的设计使io_service
不会失效,处理线程只是在等待事件发生。因此,开始设置异步链,然后在更高的范围内处理io_service
事件循环是相当普遍的。
另一方面,如果确定TCPSocket::receiveDataAsync()
应以阻塞方式处理事件循环,则考虑使用同步操作。