4

クライアントからデータを読み取るサーバーを作成し、データの読み取りにboost::asio async_read_someを使用しています。1つのハンドラー関数を作成しました。ここで_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

循環バッファーとも呼ばれるラップアラウンドバッファーの使用を検討することもできます。Boostには、テンプレートの循環バッファバージョンがあります。あなたはそれについてここで読むことができます その背後にある考え方は、いっぱいになると、物を保管する最初の部分を一周するというものです。他の構造体や配列でも同じことができます。たとえば、私は現在、アプリケーションでこの目的のためにバイト配列を使用しています。

専用の大きな循環バッファを使用してメッセージを保持する利点は、受信する新しいメッセージごとにメモリを作成および削除することを心配する必要がないことです。これにより、問題になる可能性のあるメモリの断片化が回避されます。

循環バッファの適切なサイズを決定するには、受信でき、同時に処理される段階にあるメッセージの最大数を考慮する必要があります。その数にメッセージの平均サイズを掛けてから、おそらく1.5のファッジ係数を掛けます。私のアプリケーションの平均メッセージサイズは100バイト未満です。私のバッファサイズは1メガバイトです。これにより、ラップアラウンドバッファに影響を与えることなく、少なくとも10,000件のメッセージを蓄積できます。ただし、10,000を超えるメッセージが完全に処理されずに蓄積された場合、循環バッファーは使用できなくなり、プログラムを再起動する必要があります。10,000メッセージマークに達するずっと前にシステムがおそらく死んでいるので、私はバッファのサイズを減らすことを考えていました。

于 2013-03-05T16:16:35.773 に答える
2

メモリ リークがあります。io_service pollは、あなたの_handleAsyncReceive. 他のイベント (accept など) をディスパッチする可能性があるため、 での記憶char *bufが失われます。あなたはループから呼び出していると思いますがreceiveDataAsync、必須ではありません-リークはどのような場合でも存在します(リーク速度が異なります)。

独自のパターンを作成するよりも、asio の例に従い、提案されたパターンで作業する方が良いでしょう。

于 2013-03-05T15:57:52.093 に答える
2

PSIAltが示唆するように、Boost.Asio の例に従うことを検討し、非同期プログラミングのパターンに基づいて構築してください。

それでも、複数の読み取り呼び出しを同じソケットのキューに入れる必要があるかどうかを検討することをお勧めします。アプリケーションがソケットで保留中の読み取り操作を 1 つしか許可しない場合は、リソースが削減されます。

  • で保留中のハンドラーが過剰に存在するシナリオはなくなりましたio_service
  • 1 つのバッファを事前に割り当てて、読み取り操作ごとに再利用できます。たとえば、次の非同期呼び出しチェーンは単一のバッファーのみを必要とし、前のデータが 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 に答える