18

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

サーバ:

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

クライアント:

  • サーバーからスクリーンショットを受け取ります。
  • マウス/キーボード アクションの送信;

スクリーンショットを送信することを検討してください。自宅の PC をサーバーとして使用すると、スクリーンショットのサイズが 1920x1080 になります。JAI Image I/O Toolsを使用して 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 ストリームのバッファリングを ( とBufferedInputStreamBufferedOutputStream) 両側に追加しました。私は 64 KB のサイズから始めました。これにより、実際にパフォーマンスが向上しました。しかし、少なくとも 2 FPS はまだありません。さらに、私はSocket#setReceiveBufferSizeandSocket#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 に感謝します - 彼は物事をもう少し明確にしてくれました。あなたが私のようで、ソケットのパフォーマンスを微調整する方法についての答えを探しているなら、TCP/IP Sockets in Java, Second Edition: Practical Guide for Programmers、特に第 6 章「Under the Hood」を確認する必要があります。*Socketクラス、送信バッファと受信バッファがどのように管理および利用されるか (これはパフォーマンスの主要な鍵です)。

4

5 に答える 5

11
  • 書き込み時間 ~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「ウィンドウ」のサイズを制御します。これはかなり難解なトピックですが、アイデアは、ネットワークの帯域幅と遅延の積に少なくとも等しいサイズを取得することです。つまり、帯域幅 (バイト/秒) × 遅延 (秒) >= バッファー サイズ (バイト)。

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

データを送信している間、スリープや他のタスクを行うことで混乱しないでください。あなたが手配できる最もタイトなループで、できるだけ早く送信してください。

Skype は高品質のリアルタイム デスクトップ転送を提供します - どうやってそれを行うのですか?

話題から外れており、Skype の従業員がたまたまここで会社の秘密を漏らしたい場合を除き、おそらく知ることはできません。

于 2012-05-16T09:26:32.950 に答える
8
  • クライアントがイメージごとに新しい TCP 接続を開くと、TCP スロー スタートが原因で速度が低下する可能性があります。--> すべてのフレームに同じ TCP 接続を使用します。

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

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

于 2012-05-16T09:53:19.307 に答える
2

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

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

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

于 2013-02-07T12:09:21.760 に答える
2

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

欠点は、より複雑な処理です。単純なビデオ コーデック API/実装がいくつかあるのではないでしょうか?

于 2012-05-16T09:24:26.220 に答える
0

Java NIO ( http://www.cs.brown.edu/courses/cs161/papers/j-nio-ltr.pdf ) を調べたり、複数のチャネルを使用したりできます。

于 2012-05-16T09:16:56.977 に答える