使用boost :: asio async_read_some时CPU和内存消耗很高

时间:2013-03-05 15:22:57

标签: sockets network-programming boost-asio asyncsocket

我已经创建了一个从客户端读取数据的服务器,我使用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消耗?

3 个答案:

答案 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()应以阻塞方式处理事件循环,则考虑使用同步操作。