5

大きなファイルを提供するQtでTCPサーバーを書いています。アプリケーション ロジックは次のとおりです。

  1. QTcpServer をサブクラス化し、incomingConnection(int) を再実装しました
  2. incomingConnection で、「Streamer」クラスのインスタンスを作成しています
  3. 「ストリーマー」は、incomingConnection の setSocketDescriptor で初期化された QTcpSocket を使用しています
  4. クライアントからのデータが到着したら、readyRead() スロット内から初期応答を送り返し、ソケットのシグナル bytesWritten(qint64) を Streamer のスロット bytesWritten() に接続します。

bytesWritten は次のようになります。

Streamer.h:
...
private:
    QFile *m_file;
    char m_readBuffer[64 * 1024];
    QTcpSocket *m_socket;
...

Streamer.cpp
...
void Streamer::bytesWritten() {
    if (m_socket->bytesToWrite() <= 0) {
        const int bytesRead = m_file->read(m_readBuffer, 64 * 1024);
        m_socket->write(m_readBuffer, bytesRead);   
    }
}
...

したがって、基本的に、保留中のデータがすべて完全に書き込まれたときにのみ、新しいデータを書き込んでいます。それが最も非同期的な方法だと思います。

そして、多くの同時クライアントがあるとかなり遅くなることを除いて、すべてが正しく機能します。

約5つのクライアントで-そのサーバーから約1 MB /秒の速度でダウンロードしています(自宅のインターネット接続の最大値)

約 140 クライアントの場合、ダウンロード速度は約 100 ~ 200 KB/秒です。

サーバーのインターネット接続は 10 Gbps で、140 クライアントで使用すると 100 Mbps 程度なので、問題はないと思います。

140 クライアントでのサーバーのメモリ使用量 - 2GB のうち 100 MB が利用可能

サーバーの CPU 使用率 - 最大 20%

ポート 800 を使用しています。

ポート 800 に 140 のクライアントがあり、ダウンロード速度が 100 ~ 200 KB/秒の場合、ポート 801 で別のコピーを実行し、問題なく 1 MB/秒でダウンロードしていました。

私の推測では、どういうわけか、Qt のイベント ディスパッチ (またはソケット通知機能) が遅すぎて、これらすべてのイベントを処理できないのです。

私はもう試した:

  1. Qt全体と私のアプリを-O3でコンパイルする
  2. libglib2.0-dev をインストールし、Qt を再コンパイルします (QCoreApplication は QEventDispatcherGlib または QEventDispatcherUNIX を使用するため、違いがあるかどうかを確認したかった)
  3. 特定のスレッドに現在どれだけのクライアントがいるかに応じて、streamer->moveToThread() を使用して、いくつかのスレッドを生成し、incomingConnection(int) で生成しますが、変更はありませんでした (ただし、速度ははるかにさまざまであることがわかりました)。
  4. を使用したワーカー プロセスの生成

コード:

main.cpp:
#include <sched.h>

int startWorker(void *argv) {
    int argc = 1;
    QCoreApplication a(argc, (char **)argv);

    Worker worker;
    worker.Start();

    return a.exec();
}

in main():
...
long stack[16 * 1024]; 
clone(startWorker, (char *)stack + sizeof(stack) - 64, CLONE_FILES, (void *)argv);

次に、メイン プロセスで QLocalServer を開始し、incomingConnection(int socketDescriptor) から socketDescriptors をワーカー プロセスに渡します。正常に動作しましたが、ダウンロード速度は依然として遅かったです。

また試しました:

  1. incomingConnection() の fork()-ing プロセス - サーバーをほとんど殺した :)
  2. クライアントごとに個別のスレッドを作成 - 速度が 50 ~ 100 KB/秒に低下
  3. QRunnable で QThreadPool を使用 - 違いなし

私はQt 4.8.1を使用しています

私はアイデアを使い果たしました。

それはQt関連ですか、それともサーバー構成の何かですか?

それとも、別の言語/フレームワーク/サーバーを使用する必要がありますか? ファイルを提供する TCP サーバーが必要ですが、パケット間で特定のタスクを実行する必要もあるため、その部分を自分で実装する必要があります。

4

1 に答える 1

3

ディスクの読み取りが操作をブロックしています。新しいネットワーク接続の処理など、すべての処理が停止します。ディスクの I/O スループットも有限であり、それを飽和させることができます。おそらく、ディスクがアプリケーションの残りの部分を停止させたくないでしょう。ここでQtに問題があるとは思いません-プロファイラーを実行してQtのCPU消費が過剰であること、またはQtがイベントキューでロック競合に遭遇することを示すまではそうではありません(ここで問題になるのはそれらだけです) )。

次のように、QObject 間で処理を分割する必要があります。

  1. 着信接続を受け入れます。

  2. ソケットからの書き込みと読み取りの処理。

  3. 着信ネットワーク データを処理し、ファイル以外の応答を発行します。

  4. ディスクからの読み取りとネットワークへの書き込み。

もちろん #1 と #2 は既存の Qt クラスです。

#3 と #4 を書く必要があります。おそらく、#1 と #2 をそれらの間で共有される 1 つのスレッドに移動できます。#3 と #4 は、多数のスレッドに分散する必要があります。#3 のインスタンスは、アクティブな接続ごとに作成する必要があります。次に、ファイル データを送信する時間が来ると、#3 が #4 をインスタンス化します。#4 で使用できるスレッドの数は調整可能である必要があります。おそらく、特定のワークロードに最適な設定があることがわかります。#3 と #4 は、スレッド全体でラウンドロビン方式でインスタンス化できます。ディスク アクセスがブロックされているため、#4 に使用されるスレッドは排他的であり、それ以外には使用しないでください。

#4 オブジェクトは、書き込みバッファーに残っているデータが特定の量よりも少ない場合にディスク読み取りを実行する必要があります。この量はおそらくゼロであってはなりません。可能であれば、これらのネットワーク インターフェイスを常にビジー状態に保ちたいと考えており、送信するデータが不足することは、それらをアイドル状態にする確実な方法の 1 つです。

したがって、ベンチマークに必要な次の調整可能なパラメーターが少なくとも表示されます。

  1. minNetworkWatermark - ソケット送信バッファーの最小水位。書き込むバイト数がそれより少ない場合は、ディスクから読み取り、ソケットに書き込みます。

  2. minReadSize - 最小ディスク読み取りのサイズ。ファイルの読み取りは qMax(minNetworkWatermark - socket->bytesToWrite(), minReadSize) になります。

  3. numDiskThreads - #4 オブジェクトが移動されるスレッドの数。

  4. numNetworkThreads - #3 オブジェクトが移動されるスレッドの数。

さまざまなマシンでベンチマークを実行して、処理速度とチューニングの効果を把握する必要があります。デスクトップでもノートブックでも、開発マシンからベンチマークを開始します。それはあなたの毎日の主力製品であるため、パフォーマンスに問題がある場合はすぐに気付くでしょう.

于 2012-05-29T18:46:50.353 に答える