Java TCP-сокет: передача данных медленная

Я установил сервер с ServerSocket, подключился к нему с помощью клиентской машины. Они напрямую подключаются к сети через коммутатор, а время ping составляет < 1мс.

Теперь я пытаюсь нажать "много" данных с клиента на сервер через выходной поток сокета. Для переноса 0.6Gb требуется 23 минуты. Я могу сделать намного больший файл в секундах через scp.

Любая идея, что я могу делать неправильно? Я в основном просто цикл и вызов writeInt в сокете. Проблема скорости не имеет значения, откуда поступают данные, даже если я просто отправляю постоянное целое число и не читаю с диска.

Я попытался настроить буфер отправки и получения с обеих сторон на 4Mb, без кубиков. Я использую буферный поток для читателя и писателя, без кубиков.

Я что-то пропустил?

EDIT: код

Здесь, где я делаю сокет

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.6 Гб данных.

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);
        }
    }

До сих пор кажется довольно простым...

Ответы

Ответ 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 гигабайт данных в течение более 19 секунд на моей машине. Ключ здесь используется InputStream.read и OutputStream.write, которые принимают байтовый массив в качестве параметра. Размер буфера не очень важен, он просто должен быть немного больше, чем, скажем, 5. Эксперимент с BUFFER_SIZE выше, чтобы увидеть, как он влияет на скорость, но также помните, что он, вероятно, отличается для каждой машины, на которой вы работаете эта программа включена. 64 KiB кажется хорошим компромиссом.

Ответ 2

Эй, я решил, что буду следить за тем, кого это интересует.

Здесь причудливая мораль истории:

НИКОГДА НЕ ИСПОЛЬЗУЙТЕ DataInputStream/DataOutputStream и сокеты!!

Если я переношу сокет в BufferedOutputStream/BufferedInputStream, жизнь прекрасна. Написание на него сырья просто отлично.

Но оберните сокет в DataInputStream/DataOutputStream или даже DataOutputStream (BufferedOutputStream (sock.getOutputStream())) ОЧЕНЬ МЕДЛЕННО.

Объяснение этому было бы действительно интересно для меня. Но после того, как все изменилось, это все. Попробуйте сами, если вы мне не верите.

Спасибо за всю быструю помощь.

Ответ 3

Возможно, вам стоит попробовать отправить данные ur в куски (кадры) вместо того, чтобы писать каждый байт отдельно. И выровняйте свои фреймы с размером пакета TCP для лучшей производительности.

Ответ 4

Можете ли вы попробовать сделать это через loopback, он должен затем передать данные во второй.

Если это займет несколько минут, что-то не так с вашим приложением. Если только медленная передача данных через Интернет, это может быть ваша сетевая связь, которая медленная.

Я предполагаю, что у вас сеть 10 Мбит/с между вашим клиентом и вашим сервером, и поэтому ваш перевод идет медленно. Если это так, попробуйте использовать DeflatoutOutputStream и InflatorInputStream для вашего соединения.

Ответ 5

Как вы реализуете принимающую сторону? Также отправьте свой код приема.

Поскольку протокол TCP является надежным, он предпримет шаги, чтобы убедиться, что клиент может получать все данные, отправленные отправителем. Это означает, что если ваш клиент не сможет своевременно получить данные из буфера приема данных, тогда отправляющая сторона просто перестанет отправлять больше данных, пока клиент не сможет прочитать все байты в принимающем буфере.

Если ваша принимающая сторона считывает данные по одному байту за раз, ваш отправитель, вероятно, потратит много времени, ожидая, пока буфер приема будет очищен, и, следовательно, продолжительное время передачи. Я предлагаю изменить ваш код приема на чтение как можно большего количества байтов в каждой операции чтения. Посмотрите, поможет ли это решить вашу проблему.

Ответ 6

Поскольку я еще не могу прокомментировать этот сайт, я должен написать здесь ответ @Erik.

Проблема заключается в том, что DataOutputStream не буферизует. Вся Stream-вещь в Java основана на шаблоне проектирования декораторов. Поэтому вы можете написать

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

Он будет обертывать исходный поток в BufferedOutputStream, который является более эффективным, который затем завернут в DataOutputStream, который предлагает дополнительные приятные функции, такие как writeInt(), writeLong() и т.д.

Ответ 7

@Erik: использование DataXxxputStream здесь не является проблемой. Проблема в том, что вы отправляли данные в слишком мелкие куски. Использование буфера решило вашу проблему, потому что даже вы будете писать поэтапно, буфер решит проблему. Решение Bombe намного лучше, родовое и быстрое.

Ответ 8

Вы должны скачать хороший сниффер пакетов. Я большой поклонник WireShark лично, и я в конечном итоге использую его каждый раз, когда я занимаюсь программированием сокетов. Просто имейте в виду, что вам нужно, чтобы клиент и сервер работали в разных системах, чтобы получить любые пакеты.

Ответ 9

Что нужно попробовать:

  • Является ли процессор 100% при отправке данных? Если это так, используйте visualvm и выполните профилирование ЦП, чтобы узнать, где потрачено время.
  • Использование SocketChannel из java.nio - это, как правило, быстрее, поскольку они могут использовать собственный IO более легко - конечно это только помогает, если ваша операция связана с процессором.
  • Если это не связано с ЦП, на сетевом уровне что-то происходит не так. Используйте анализатор пакетов, чтобы проанализировать это.

Ответ 10

Я использовал PrintWriter для отправки данных. Я удалил это и отправил данные с BufferedOutputStream.send(String.getBytes()) и получил примерно в 10 раз быстрее отправки.

Ответ 11

Как устанавливается размер вашей кучи? Недавно у меня была аналогичная проблема с передачей сокетов больших объемов данных, и просто взглянув на JConsole, я понял, что приложение тратит большую часть времени на выполнение полных GC.

Попробуйте -Xmx1g

Ответ 12

USe Байт-буфер для отправки данных