web-dev-qa-db-ja.com

Javaソケットのパフォーマンスの微調整

リモートデスクトップコントロールアプリケーションを作成しました。明らかに、クライアントとサーバーの部分で構成されています。

サーバー:

  • クライアントからのマウス/キーボードアクションの受信。
  • デスクトップのスクリーンショットをクライアントに送信しています。

クライアント:

  • サーバーからスクリーンショットを受信して​​います。
  • マウス/キーボードアクションの送信。

スクリーンショットを送信することを検討してください。自宅のPCをサーバーとして使用すると、1920x1080のスクリーンショットのサイズになります。 JAI画像I/Oツール を使用してPNGとしてエンコードすることで、このような大きな画像に対して次の統計を達成できました。

  1. 書き込み時間〜0.2秒。 (ソケットではなく、いくつかの「通常の」出力ストリームに、つまりエンコード時間
  2. 読み取り時間〜0.05秒; (ソケットからではなく、「通常の」入力ストリームから、つまりデコード時間
  3. サイズ〜250 KB;
  4. 完璧な品質。

その結果、#1によると-理想的なFPSは5以下である必要があります。

残念ながら、私はそれらの〜5 FPSでさえ、2 FPSでさえ達成することができません。ボトルネックを検索したところ、ソケットI/Oストリームへの書き込み/読み取りに最大2秒(説明については、付録1および2を参照してください)。確かにそれは受け入れられません。

私はこのトピックを少し調査し、ソケットのI/Oストリームのバッファリングを(BufferedInputStreamおよびBufferedOutputStreamを使用して)両側に追加しました。私は64 KBのサイズから始めました。これにより、実際にパフォーマンスが向上しました。しかし、それでも最低2 FPSはありません!さらに、Socket#setReceiveBufferSizeSocket#setSendBufferSizeを試してみましたが、速度にいくつかの変更がありましたが、それらの動作が正確にわからないため、使用する値がわかりません。

初期化コードを見てください:

サーバー:

    ServerSocket serverSocket = new ServerSocket();
    serverSocket.setReceiveBufferSize( ? ); // #1
    serverSocket.bind(new InetSocketAddress(...));

    Socket clientSocket = serverSocket.accept();
    clientSocket.setSendBufferSize( ? ); // #2
    clientSocket.setReceiveBufferSize( ? ); // #3

    OutputStream outputStream = new BufferedOutputStream(
            clientSocket.getOutputStream(), ? ); // #4
    InputStream inputStream = new BufferedInputStream(
            clientSocket.getInputStream(), ? ); // #5

クライアント:

    Socket socket = new Socket(...);
    socket.setSendBufferSize( ? ); // #6
    socket.setReceiveBufferSize( ? ); // #7

    OutputStream outputStream = new BufferedOutputStream(
            socket.getOutputStream(), ? ); // #8
    InputStream inputStream = new BufferedInputStream(
            socket.getInputStream(), ? ); // #9

質問:

  1. これらすべてのケースについて、(パフォーマンスを向上させるために)どの値をお勧めしますか、またその理由は何ですか。
  2. Socket#setReceiveBufferSizeおよびSocket#setSendBufferSizeの動作を明確にしてください。
  3. そのようなアプリケーションのパフォーマンスを改善するために、他にどのような方法/テクニックをアドバイスできますか?
  4. Skypeは高品質のリアルタイムデスクトップ転送を提供します。

付録1:クライアントソケットの読み取りの展開された疑似コードを追加(@mcfinnigan):

while(true) {
    // objectInputStream is wrapping socket's buffered input stream.
    Object object = objectInputStream.readObject(); // <--- Bottleneck (without setting proper buffer sizes is ~2 s)

    if(object == null)
        continue;

    if(object.getClass() == ImageCapsule.class) {
        ImageCapsule imageCapsule = (ImageCapsule)object;

        screen = imageCapsule.read(); // <--- Decode PNG (~0.05 s)

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                repaint();
            }
        });
    }
}

付録2:サーバーソケット書き込みのアンロールされた疑似コードを追加(@EJP):

while(true) {
    // objectOutputStream is wrapping socket's buffered output stream.
    BufferedImage screen = ... // obtaining screenshot
    ImageCapsule imageCapsule = new ImageCapsule();

    imageCapsule.write(screen, formatName()); // <--- Encode PNG (~0.2 s)

    try {
        objectOutputStream.writeObject(imageCapsule); // <--- Bottleneck (without setting proper buffer sizes is ~2 s)
    }
    finally {
        objectOutputStream.flush();
        objectOutputStream.reset(); // Reset to free written objects.
    }
}

結論:

回答ありがとうございます。特にEJP-彼は私に少し明確にしてくれました。あなたが私のような場合-ソケットのパフォーマンスを微調整する方法についての回答を求める場合は、必ず確認する必要があります JavaのTCP/IPソケット、第2版:プログラマ向け実用ガイド 、特に第6章「裏側」 *Socketクラスの舞台裏で何が起こるか、送信バッファと受信バッファがどのように管理および利用されるかを説明します(これらはパフォーマンスの主キーです)。

18
  • 書き込み時間〜0.2秒。
  • 読み取り時間〜0.05秒;

これらの目標は、介在するネットワークの遅延と帯域幅を考慮しないと、まったく意味がありません。

サイズ〜250 KB;

オフトピック。画像のサイズはあなた次第であり、このサイトの目的である実際のプログラミングとは関係ありません。

完璧な品質。

「完璧な品質」は、ビットを落とさないことだけを要求します。それは、とにかくTCP経由では得られません。

_  serverSocket.setReceiveBufferSize( ? ); // #1
_

これにより、受け入れられるすべてのソケットの受信バッファーサイズが設定されます。できるだけ大きく、可能であれば64k以上に設定してください。

_socket.setSendBufferSize( ? ); // #6
_

これをできるだけ余裕のある大きさに設定し、可能であれば64kを超えてください。

_    socket.setReceiveBufferSize( ? ); // #7
_

これは受け入れ済みのソケットなので、すでに上記でこれを行っています。削除する。

_    OutputStream outputStream = new BufferedOutputStream(
            socket.getOutputStream(), ? ); // #8
    InputStream inputStream = new BufferedInputStream(
            socket.getInputStream(), ? ); // #9
_

これらのデフォルトは8kです。あなたが十分でなければならないまともなソケットバッファサイズがある限り。

これらすべてのケースについて、(パフォーマンスを向上させるために)どの値をお勧めしますか、またその理由は何ですか。

上記を参照。

Socket#setReceiveBufferSize()およびSocket#setSendBufferSize()の動作を明確にしてください。

それらはTCP 'window'のサイズを制御します。これはかなり厄介なトピックですが、アイデアは、少なくともネットワークの帯域幅遅延積、つまりバイト単位の帯域幅と等しいサイズを取得することです/ second秒の遅延時間> =バッファサイズ(バイト単位)。

そのようなアプリケーションのパフォーマンスを改善するために、他にどのような方法/テクニックをアドバイスできますか?

データの送信中に、スリープ状態で他のタスクを実行したりしないでください。アレンジできる最もタイトなループで、できるだけ早く送信してください。

Skypeは高品質のリアルタイムデスクトップ転送を提供します。

Skypeの従業員がたまたまここで会社の秘密を明かしたいと思わない限り、話題外でおそらくおそらく分からない。

11
user207421
  • クライアントが新しい画像を開く場合TCP各画像の接続、 TCPスロースタート が原因で速度が低下する可能性があります。->同じものを使用TCPすべてのフレームの接続。

  • TCPには、TCP-> BufferedOutputStreamに最適な方法で使用される独自のバッファーがあります。

  • 通常、スクリーンショットの間で変更されるのは画面のごく一部だけです。 ->変更された部品のみを転送します。

7
SKi

最大の帯域幅の無駄は、デスクトップの完全な転送にあると思います。これをフィルムのように扱い、フレームの差分エンコーディングを行う必要があります。

欠点は、より複雑な処理です。多分そこにいくつかの簡単なビデオコーデックAPI /実装がありますか?

2
mtraut

質問は明確です。私の上の誰かが複数のnioチャネルの使用を提案し、私はむしろ複数のブロッキングソケットを使用したいと思います(nioは高速ではありません)。ただし、これはネット通信を改善するものではありません。これは並列プログラミングであり、間違いなくあなたの場合に良い解決策です。以下を提案させてください

http://docs.Oracle.com/javase/1.4.2/docs/api/Java/net/Socket.html#setTcpNoDelay(boolean)

これをtrueに設定すると、帯域幅の消費量が増加する代わりに、ソケット通信を高速化できます。

0
user1562591

Java NIO( http://www.cs.brown.edu/courses/cs161/papers/j-nio-ltr.pdf )、複数のチャネルを使用する、など。

0