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

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

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

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

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

Я что-то упускаю?

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

До сих пор это довольно просто …

Вы не хотите писать одиночные байты при передаче больших объемов данных.

 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 кажутся хорошим компромиссом.

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

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

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

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

Но оберните сокет в DataInputStream / DataOutputStream или даже DataOutputStream (BufferedOutputStream (sock.getOutputStream ())) является Чрезвычайно медленным.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Попробовать -Xmx1g

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

Давайте будем гением компьютера.