2013-09-12 1 views
1

Итак, я делаю Java-программу с сервером и клиентом, и я отправляю Zip-файл с сервера на клиент. Я почти полностью отсылаю файл. Но, получив, я обнаружил некоторую несогласованность. Мой код не всегда получает полный архив. Я предполагаю, что это заканчивается до того, как BufferedReader имеет всю полноту. Вот код для клиента:Как я могу убедиться, что я получил весь файл через поток сокетов?

public void run(String[] args) { 
     try { 
      clientSocket = new Socket("jacob-custom-pc", 4444); 
      out = new PrintWriter(clientSocket.getOutputStream(), true); 
      in = new BufferedInputStream(clientSocket.getInputStream()); 
      BufferedReader inRead = new BufferedReader(new InputStreamReader(in)); 
      int size = 0; 
      while(true) { 

       if(in.available() > 0) {  

         byte[] array = new byte[in.available()]; 
         in.read(array); 
         System.out.println(array.length); 


         System.out.println("recieved file!"); 
         FileOutputStream fileOut = new FileOutputStream("out.zip"); 
         fileOut.write(array); 
         fileOut.close(); 
         break; 
        } 
       } 
      } 
     } catch(IOException e) { 
      e.printStackTrace(); 
      System.exit(-1); 
     } 
    } 

Итак, как я могу быть уверен, что весь архив существует до того, как он напишет файл?

ответ

1

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

+0

Зачем нужен размер файла, если TCP является надежным протоколом, и он говорит, что вы используете endofstream, чтобы вы сами могли определить размер (и вам это не нужно на самом деле)? – Val

+0

@Val Конец потока работает только в том случае, если сокет закрыт после записи файла. Это может быть достаточно для его простого приложения. В общем случае отправки нескольких вещей по одному соединению вам нужен другой способ сделать это. – stonemetal

+0

Отлично, я закончил тем, что использовал этот подход. Он выполняет цикл do-while до тех пор, пока общее количество байтов не будет равно количеству отправленных вперед байтов. Благодаря главному - и Валу тоже за помощь. Вы, ребята, намного лучше, чем я. :) – Jadar

-1

Добавить символ завершения в конце файла при его отправке. Я считаю, что Unix использует -1 для обозначения конца файла. При получении файла подождите, пока не найдете -1 и не удалите его.

+0

Unix? Почему теперь Windows или Java? – Val

+0

Ни Unix, ни Windows, ни Java не добавляют завершающий символ в конец файла. Java-метод 'read()' возвращает * -1 в конце потока, но это внеполосные данные: он не исходит из файла или равноправный в случае сокета. Он приходит в EOF или когда партнер закрывает соединение в случае сокета. -1 – EJP

0

HTTP отправляет content-length: x + \ n в байтах. Это изящно, оно может вызывать исключение TimeoutException, если соединение не работает.

+0

Этот вопрос не о HTTP. –

+1

Мой ответ не «Использовать HTTP», мой ответ: HTTP использует элегантное решение для этой хорошо известной проблемы. –

+2

То есть, ваш ответ - вы говорите, что ваш ответ - комментарий, а не ответ? – Val

0

Вы используете сокет TCP. ZIP-файл, вероятно, больше, чем сетевой MTU, поэтому он будет разделен на несколько пакетов и снова собран на другой стороне. Тем не менее, что-то подобное может случиться:

  • клиент подключается
  • сервер начинает посылать. ZIP-файл больше MTU и поэтому разбивается на несколько пакетов.
  • клиент занят-ждет в while (true), пока он не получит первые пакеты.
  • клиент замечает, что данные пришли (in.available() > 0)
  • клиент читает все доступные данные, записывает его в файл и выходы
  • последние пакеты прибывают

Так как вы можете видеть : Если клиентская машина безумно медленна, а сеть безумно быстро и имеет огромный MTU, ваш код просто не получит весь файл по дизайну. Вот как вы его построили.

Другой подход: префикс данных с длиной.

Socket clientSocket = new Socket("jacob-custom-pc", 4444); 
DataInputStream dataReader = new DataInputStream(clientSocket.getInputStream()); 
FileOutputStream out = new FileOutputStream("out.zip"); 
long size = dataReader.readLong(); 
long chunks = size/1024; 
int lastChunk = (int)(size - (chunks * 1024)); 
byte[] buf = new byte[1024]; 
for (long i = 0; i < chunks; i++) { 
    dataReader.read(buf); 
    out.write(buf); 
} 
dataReader.read(buf, 0, lastChunk); 
out.write(buf, 0, lastChunk); 

И сервер использует DataOutputStream отправить размер файла до фактического файла. Я не тестировал это, но он должен работать.

+0

Что такое MTU. TCP означает, что существует соединение. Вы имеете в виду, что соединение закрывается таймаутом, и это сообщается, как будто отправитель закрыл сокет сглаженно? – Val

+0

Используйте Google. Пожалуйста. http://en.wikipedia.org/wiki/Maximum_Transmission_Unit –

+1

Нет таймаута, вы просто создали свой код, чтобы прекратить получать данные после того, как первые части прибыли. –

-1

In.available() просто говорит вам, что нет данных, которые должны быть использованы in.read() без блокировки (ожидания) в данный момент, но это не означает конец потока. Но они могут появиться на вашем ПК в любое время с пакетом TCP/IP. Обычно вы никогда не используете in.available(). In.read() хватает всего на то, чтобы полностью прочитать поток.Образец для считывания входных потоков:

byte[] buf; 
int size; 

while ((size = in.read(buf)) != -1) 
    process(buf, size); 

// end of stream has reached 

Таким образом, вы полностью прочитаете поток до его конца.

update Если вы хотите прочитать несколько файлов, то вы можете передать поток в «пакеты» и префикс каждого с целым размером. Затем вы читаете до получения байтов размера вместо in.read = -1.

update2 В любом случае, никогда не используйте in.available для демаркинга между кусками данных. Если вы это сделаете, вы подразумеваете, что есть временная задержка между входящими частями данных. Вы можете сделать это только в системах реального времени. Но Windows, Java и TCP/IP - все эти уровни несовместимы с реальным временем.

+0

Что делать, если он думает, что поток сделан, но есть больше входящих байтов? – Jadar

+0

@Jadar Конец потока означает, что сокет закрыт, поэтому количество входящих байтов больше не будет. Единственным реальным недостатком является то, что клиент не может определить разницу между успехом и ошибкой из-за потери соединения. – stonemetal

+0

Что делать, если я хочу сохранить сокет открытым для большего количества передач? – Jadar

0

Как я могу убедиться, что получил весь файл через поток сокетов?

Установив код. Вы используете InputStream.available() в качестве теста для конца потока. Это не то, для чего. Изменение цикла копирования на это, что также намного проще:

while ((count = in.read(buffer)) > 0) 
{ 
    out.write(buffer, 0, count); 
} 

Использование любого размера буфера больше нуля, как правило, 8192.

Смежные вопросы