4

我已经制作了一个从客户端读取数据的服务器,并且我正在使用 boost::asio async_read_some 来读取数据,并且我已经制作了一个处理函数,这里 _ioService->poll() 将运行事件处理循环来执行准备好的处理程序。在处理程序 _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 消耗?

4

3 回答 3

2

你有内存泄漏。io_service poll不保证它与调度你的_handleAsyncReceive. 它可以调度其他事件(例如接受),因此您的记忆char *buf丢失了。我猜你是receiveDataAsync从一个循环中调用的,但它不是必需的 - 在任何情况下都会存在泄漏(具有不同的泄漏速度)。

如果您遵循asio 示例并使用建议的模式而不是自己创建模式,那就更好了。

于 2013-03-05T15:57:52.093 回答
2

正如PSIAlt建议的那样,考虑遵循 Boost.Asio示例并在它们的异步编程模式上进行构建。

不过,我建议考虑是否需要将多个读取调用排队到同一个套接字上。如果应用程序只允许单个读取操作在套接字上挂起,那么资源就会减少:

  • 不再存在在io_service.
  • 可以为每个读取操作预先分配和重用单个缓冲区。例如,下面的异步调用链只需要一个缓冲区,并允许在 Qt 信号上发出先前的数据时并发执行启动异步读取操作,就像QString执行深度复制一样。

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

于 2013-03-05T18:10:34.923 回答
2

您可以考虑使用环绕缓冲区,也称为循环缓冲区。Boost 有一个可用的模板循环缓冲区版本。你可以在这里阅读。 它背后的想法是,当它变满时,它会绕到开始存储东西的地方。您也可以对其他结构或数组执行相同的操作。例如,我目前在我的应用程序中为此目的使用字节数组。

使用专用的大型循环缓冲区来保存消息的优点是您不必担心为每条传入的新消息创建和删除内存。这可以避免内存碎片,这可能会成为一个问题。

要确定循环缓冲区的适当大小,您需要考虑可以进入并处于同时处理的某个阶段的最大消息数;将该数字乘以消息的平均大小,然后乘以可能为 1.5 的软糖因子。我的应用程序的平均消息大小低于 100 字节。我的缓冲区大小为 1 兆字节,这将允许至少 10,000 条消息累积,而不会影响环绕缓冲区。但是,如果超过 10,000 条消息确实累积而没有完全处理,那么循环缓冲区将无法使用,程序将不得不重新启动。我一直在考虑减小缓冲区的大小,因为系统可能在达到 10,000 条消息标记之前很久就死机了。

于 2013-03-05T16:16:35.773 回答