web-dev-qa-db-ja.com

Java TCPソケット:データ転送が遅い

ServerSocketを使用してサーバーをセットアップし、クライアントマシンを使用してサーバーに接続します。これらはスイッチを介して直接ネットワーク化されており、ping時間は1ミリ秒未満です。

ここで、ソケットの出力ストリームを介してクライアントからサーバーに「大量の」データをプッシュしようとします。 0.6Gbの転送には23分かかります。 scpを介してはるかに大きなファイルを数秒でプッシュできます。

私が間違っているかもしれない何か考えはありますか?基本的には、ソケットでwriteIntをループして呼び出しています。定数整数を送信し、ディスクから読み取っていなくても、データの送信元は速度の問題ではありません。

両側の送信バッファと受信バッファをダイスなしで4Mbに設定してみました。私はリーダーとライターにバッファリングされたストリームを使用します。サイコロは使用しません。

私は何かが足りないのですか?

編集:コード

これが私がソケットを作るところです

System.out.println("Connecting to " + hostname);

    serverAddr = InetAddress.getByName(hostname);

    // connect and wait for port assignment
    Socket initialSock = new Socket();
    initialSock.connect(new InetSocketAddress(serverAddr, LDAMaster.LDA_MASTER_PORT));
    int newPort = LDAHelper.readConnectionForwardPacket(new DataInputStream(initialSock.getInputStream()));
    initialSock.close();
    initialSock = null;

    System.out.println("Forwarded to " + newPort);

    // got my new port, connect to it
    sock = new Socket();
    sock.setReceiveBufferSize(RECEIVE_BUFFER_SIZE);
    sock.setSendBufferSize(SEND_BUFFER_SIZE);
    sock.connect(new InetSocketAddress(serverAddr, newPort));

    System.out.println("Connected to " + hostname + ":" + newPort + " with buffers snd=" + sock.getSendBufferSize() + " rcv=" + sock.getReceiveBufferSize());

    // get the MD5s
    try {
        byte[] dataMd5 = LDAHelper.md5File(dataFile),
               indexMd5 = LDAHelper.md5File(indexFile);

        long freeSpace = 90210; // ** TODO: actually set this **

        output = new DataOutputStream(new BufferedOutputStream(sock.getOutputStream()));
        input  = new DataInputStream(new BufferedInputStream(sock.getInputStream()));

サーバー側の接続を行う場所は次のとおりです。

    ServerSocket servSock = new ServerSocket();
    servSock.setSoTimeout(SO_TIMEOUT);
    servSock.setReuseAddress(true);
    servSock.bind(new InetSocketAddress(LDA_MASTER_PORT));

    int currPort = LDA_START_PORT;

    while (true) {
        try {
            Socket conn = servSock.accept();
            System.out.println("Got a connection.  Sending them to port " + currPort);
            clients.add(new MasterClientCommunicator(this, currPort));
            clients.get(clients.size()-1).start();

            Thread.sleep(500);

            LDAHelper.sendConnectionForwardPacket(new DataOutputStream(conn.getOutputStream()), currPort);

            currPort++;
        } catch (SocketTimeoutException e) {
            System.out.println("Done listening.  Dispatching instructions.");
            break;
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

了解しました。ここで、約0.6Gbのデータを出荷しています。

public static void sendTermDeltaPacket(DataOutputStream out, TIntIntHashMap[] termDelta) throws IOException {
    long bytesTransferred = 0, numZeros = 0;

    long start = System.currentTimeMillis();

    out.write(PACKET_TERM_DELTA); // header     
    out.flush();
    for (int z=0; z < termDelta.length; z++) {
        out.writeInt(termDelta[z].size()); // # of elements for each term
        bytesTransferred += 4;
    }

    for (int z=0; z < termDelta.length; z++) {
        for (int i=0; i < termDelta[z].size(); i++) {
            out.writeInt(1);
            out.writeInt(1);
        }
    }

これまでのところかなり簡単に思えます...

15
Erik

大量のデータを転送する場合、notは1バイトを書き込みたくありません。

import Java.io.FileInputStream;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.net.ServerSocket;
import Java.net.Socket;

public class Transfer {

    public static void main(String[] args) {
        final String largeFile = "/home/dr/test.dat"; // REPLACE
        final int BUFFER_SIZE = 65536;
        new Thread(new Runnable() {
            public void run() {
                try {
                    ServerSocket serverSocket = new ServerSocket(12345);
                    Socket clientSocket = serverSocket.accept();
                    long startTime = System.currentTimeMillis();
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int read;
                    int totalRead = 0;
                    InputStream clientInputStream = clientSocket.getInputStream();
                    while ((read = clientInputStream.read(buffer)) != -1) {
                        totalRead += read;
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println(totalRead + " bytes read in " + (endTime - startTime) + " ms.");
                } catch (IOException e) {
                }
            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000);
                    Socket socket = new Socket("localhost", 12345);
                    FileInputStream fileInputStream = new FileInputStream(largeFile);
                    OutputStream socketOutputStream = socket.getOutputStream();
                    long startTime = System.currentTimeMillis();
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int read;
                    int readTotal = 0;
                    while ((read = fileInputStream.read(buffer)) != -1) {
                        socketOutputStream.write(buffer, 0, read);
                        readTotal += read;
                    }
                    socketOutputStream.close();
                    fileInputStream.close();
                    socket.close();
                    long endTime = System.currentTimeMillis();
                    System.out.println(readTotal + " bytes written in " + (endTime - startTime) + " ms.");
                } catch (Exception e) {
                }
            }
        }).start();
    }
}

これにより、1 GiBのデータが19秒以上で私のマシンにコピーされます。ここで重要なのは InputStream.read および OutputStream.write を使用することです。 =バイト配列をパラメータとして受け入れるメソッド。バッファのサイズはそれほど重要ではありません。たとえば、5より少し大きくする必要があります。上記のBUFFER_SIZEを試して、速度にどのように影響するかを確認しますが、次の点にも注意してください。このプログラムを実行しているマシンごとに異なる可能性があります。64KiBは適切な妥協点のようです。

26
Bombe

ねえ、私は興味のある人のためにフォローアップすると思いました。

これが物語の奇妙な教訓です:

DataInputStream/DataOutputStreamとソケットは絶対に使用しないでください!!

ソケットをBufferedOutputStream/BufferedInputStreamでラップすると、人生は素晴らしいものになります。生で書くのは問題ありません。

ただし、ソケットをDataInputStream/DataOutputStreamでラップするか、DataOutputStream(BufferedOutputStream(sock.getOutputStream()))が非常に遅い場合もあります。

その説明は私にとって本当に興味深いものになるでしょう。しかし、すべてを交換した後、これが問題になっています。信じられないなら、自分で試してみてください。

しかし、すべての迅速な助けに感謝します。

12
Erik

各バイトを個別に書き込むのではなく、urデータをチャンク(フレーム)で送信してみてください。そして、最高のパフォーマンスを得るために、フレームをTCPパケットサイズに合わせます。

6
Midhat

このサイトにはまだコメントできないので、@ Erikへの回答をここに書く必要があります。

問題は、DataOutputStreamがバッファリングしないことです。 JavaのStream-thingは、デコレータのデザインパターンに基づいています。

DataOutputStream out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));

元のストリームをより効率的なBufferedOutputStreamにラップし、DataOutputStreamにラップして、writeInt()、writeLong()などの追加のNice機能を提供します。

2
lpapez

ループバックを介してこれを実行してみてください。2番目にデータが転送されます。

数分かかる場合は、アプリケーションに問題があります。インターネットを介したデータの送信が遅い場合は、ネットワークリンクが遅い可能性があります。

私の推測では、クライアントとサーバーの間に10 Mb/sのネットワークがあり、これが転送の速度が遅い理由です。この場合は、接続にDeflatoutOutputStreamとInflatorInputStreamを使用してみてください。

2
Peter Lawrey

受信側をどのように実装していますか?受信コードも投稿してください。

TCPは信頼できるプロトコルであるため、クライアントが送信者から送信されたすべてのデータを受信できるようにするための手順が実行されます。つまり、クライアントがデータを取得できない場合は、データ受信バッファが時間内にある場合、送信側は、クライアントが受信バッファ内のすべてのバイトを読み取る機会が得られるまで、それ以上のデータの送信を停止します。

受信側が一度に1バイトずつデータを読み取っている場合、送信側は受信バッファがクリアされるのを待つのに多くの時間を費やす可能性があるため、転送時間が長くなります。提案します各読み取り操作でできるだけ多くのバイトを読み取るように受信コードを変更します。それがあなたの問題を解決するかどうか見てください。

2
futureelite7

@Erik:DataXxxputStreamの使用はここでは問題ではありません。問題は、データを小さすぎるチャンクで送信していたことです。バッファを使用すると問題が解決しました。ビットごとに書き込む場合でも、バッファが問題を解決するからです。 Bombeのソリューションは、はるかに優れており、一般的で高速です。

1
ignasi35

優れたパケットスニファをダウンロードする必要があります。私は個人的に WireShark の大ファンであり、ソケットプログラミングを行うたびにそれを使用することになります。パケットを取得するには、クライアントとサーバーを異なるシステムで実行する必要があることに注意してください。

0
Spencer Ruport

試すべきこと:

  • データ送信中のCPUは100%ですか?その場合は、visualvmを使用し、CPUプロファイリングを実行して、時間が費やされている場所を確認します
  • Java.nioのSocketChannelを使用します-ネイティブIOをより簡単に使用できるため、これらは一般的に高速です-もちろん、これは操作がCPUバウンドの場合にのみ役立ちます
  • CPUにバインドされていない場合は、ネットワークレベルで問題が発生しています。これを分析するには、パケットスニファを使用します。
0

PrintWriterを使用してデータを送信していました。それを削除し、BufferedOutputStream.send(String.getBytes())を使用してデータを送信すると、送信が約10倍速くなりました。

0
Juubes