DownloadManager на Java
От: wirt  
Дата: 25.07.07 13:51
Оценка:
Стоит задача написать некое подобие DownloadManager'a на Java.
Т.е. на вход подается некий удаленный файл, который мы читаем в несколько потоков и записываем на локальный диск.
Технические сложности возникли с чтением файла в несколько потоков и соответственно с записью файла в несколько потоков...
Как я делаю:
разбиваю файл на N частей, каждая часть читается с определенной позиции и пишется в соответственную позицию отдельным потоком.
Для чтения с определенного места использую
InputStream.mark()
, для записи
RandomAccessFile.seek()
.
Но в результате выполнения получаю фантомные IOExceptions Write error. Возможно это результат проблемы с синхронизацией.

Вопрос, поддерживает ли Java многопоточное чтение с одного InputStream (markable()==true), и поддерживается многопоточная запись в RandomAccessFile? Какие есть другие альтернативы и\или возможные причины проблемы?
Re: DownloadManager на Java
От: C0s Россия  
Дата: 25.07.07 14:05
Оценка:
Здравствуйте, wirt, Вы писали:

W>Стоит задача написать некое подобие DownloadManager'a на Java.


за нас уже написали: Sun Download Manager
Re: DownloadManager на Java
От: Blazkowicz Россия  
Дата: 25.07.07 14:23
Оценка:
Здравствуйте, wirt, Вы писали:

W>Стоит задача написать некое подобие DownloadManager'a на Java.

Откуда задача. Гугл смело выдает несколько решений причем даже Opensource. Можно подчерпнуть много интересного.

W>Т.е. на вход подается некий удаленный файл, который мы читаем в несколько потоков и записываем на локальный диск.

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



W>Для чтения с определенного места использую
InputStream.mark()
,

Кто надоумил? Пробовал прочитать внимательно доку к этому методу и обдумать?

W>для записи
RandomAccessFile.seek()
.

ОДин экземпляр или разные?

W>Но в результате выполнения получаю фантомные IOExceptions Write error. Возможно это результат проблемы с синхронизацией.

Ну, прям уж фантомные. Один залочил на запись. Другой не справился с записью. А ещё вероятно что один поток переместит указатель, а другой не зная этого будет писать в непонятно куда.

W>Вопрос, поддерживает ли Java многопоточное чтение с одного InputStream (markable()==true), и поддерживается многопоточная запись в RandomAccessFile? Какие есть другие альтернативы и\или возможные причины проблемы?

Первая проблема это не штудирование гугла. Вторая проблема это ниодного намека на java.nio. Посмотри FileChannel например.
Re: DownloadManager на Java
От: ynix  
Дата: 25.07.07 16:09
Оценка: 1 (1)
Как бы я делал. В файл пишет только одна нить. Файл принимают по сети несколько других нитей. Периодически эти "приемники" обращатся к файловому писателю с тем, чтобы скинуть свои данные на диск. Эти "обращения" синхронизированные, естественно.
Re[2]: DownloadManager на Java
От: wirt  
Дата: 26.07.07 07:54
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

Спасибо за ответы.

W>>Т.е. на вход подается некий удаленный файл, который мы читаем в несколько потоков и записываем на локальный диск.

B>Надо таки конкретизировать что за "удаленный файл". Очень не стоит абстрагироваться от такой вещи как протокол. Потому как для многих протоколов понятие "читаем в несколько потоков" может оказатся не приемлимым.

Для начала пусть будет файн на Http сервере.


W>>Для чтения с определенного места использую
InputStream.mark()
,

B>Кто надоумил? Пробовал прочитать внимательно доку к этому методу и обдумать?

ошибку понял. Хочу использовать Channels, но не знаю как его инстанциировать от InputStream полученногго urlConnection.getInputStream()

W>>для записи
RandomAccessFile.seek()
.

B>ОДин экземпляр или разные?

Пробовал и так и так. Логично что должны быть разные... Заменил на N instance-oв FileChannel, Это решение будет Thread Safe? Или надо блоки синхронизации добавлять?
Re[2]: DownloadManager на Java
От: wirt  
Дата: 26.07.07 07:57
Оценка:
Здравствуйте, C0s, Вы писали:

C0s>Здравствуйте, wirt, Вы писали:


W>>Стоит задача написать некое подобие DownloadManager'a на Java.


C0s>за нас уже написали: Sun Download Manager


дык сорцов-то там нету...
Re[2]: DownloadManager на Java
От: wirt  
Дата: 26.07.07 08:01
Оценка:
Здравствуйте, ynix, Вы писали:

Y>Как бы я делал. В файл пишет только одна нить. Файл принимают по сети несколько других нитей. Периодически эти "приемники" обращатся к файловому писателю с тем, чтобы скинуть свои данные на диск. Эти "обращения" синхронизированные, естественно.


Т.е. читать их в ByteArrayStream(?), а потом как только заполнятся на N скидывать на диск?
Re[3]: DownloadManager на Java
От: wirt  
Дата: 26.07.07 10:07
Оценка:
Здравствуйте, wirt, Вы писали:

W>Здравствуйте, C0s, Вы писали:


C0s>>Здравствуйте, wirt, Вы писали:


W>>>Стоит задача написать некое подобие DownloadManager'a на Java.


C0s>>за нас уже написали: Sun Download Manager


W>дык сорцов-то там нету...


Нашел такой код:

// Open connection to URL.
            HttpURLConnection connection = (HttpURLConnection) url
                    .openConnection();

            // Specify what portion of file to download.
            connection.setRequestProperty("Range", "bytes=" + downloaded + "-");


правильно ли я понимаю, что этим параметром можно регулировать с какой позиции читать? где можно подробно прочитать про этот и другие параметры? все ли веб серверы его поддерживают?

Второй вопрос:
как быть в том случае, когда contentLength == -1? как можно узнать длину файла в этом случае?
Re[4]: DownloadManager на Java
От: mikkri Великобритания  
Дата: 26.07.07 10:15
Оценка:
Здравствуйте, wirt, Вы писали:

W>>>>Стоит задача написать некое подобие DownloadManager'a на Java.


W>правильно ли я понимаю, что этим параметром можно регулировать с какой позиции читать? где можно подробно прочитать про этот и другие параметры? все ли веб серверы его поддерживают?


Только так и можно делать. Читайте спецификацию HTTP

W>Второй вопрос:

W>как быть в том случае, когда contentLength == -1? как можно узнать длину файла в этом случае?

Качать по обычному, без многопоточности.
Re[3]: DownloadManager на Java
От: Blazkowicz Россия  
Дата: 26.07.07 10:20
Оценка:
Здравствуйте, wirt, Вы писали:

W>дык сорцов-то там нету...


здесь есть
Re: DownloadManager на Java
От: mikkri Великобритания  
Дата: 26.07.07 10:22
Оценка: 1 (1)
Здравствуйте, wirt, Вы писали:

W>Стоит задача написать некое подобие DownloadManager'a на Java.

W>Т.е. на вход подается некий удаленный файл, который мы читаем в несколько потоков и записываем на локальный диск.
W>Технические сложности возникли с чтением файла в несколько потоков и соответственно с записью файла в несколько потоков...

ИМХО, до технических сложностей вы еще не дошли. Вам нужно для начала понять, что за задача перед вами стоит.

Начнем с того, что в протоколе HTTP нет понятия файла. Соответственно, не понятно, по какому протоколу вы этот удаленный файл читаете. Далее, не понятно, можно ли этот удаленный файл читать частями и есть ли от этого хоть какая-то польза. Далее, есть ли возможность читать файл с середины, если вы уже прочитали половину, но потеряли связь?
Запись на диск тут самая простая подзадача, я вам скажу.

И, прежде чем мудрить, стоит понять, зачем вы этот DownloadManager пишете, какая у него задача в терминах пользователя?
Re[4]: DownloadManager на Java
От: Blazkowicz Россия  
Дата: 26.07.07 10:23
Оценка:
Здравствуйте, wirt, Вы писали:

W>правильно ли я понимаю, что этим параметром можно регулировать с какой позиции читать? где можно подробно прочитать про этот и другие параметры?

HTTP Headers RFC.

W>все ли веб серверы его поддерживают?

Нет.

W>Второй вопрос:

W>как быть в том случае, когда contentLength == -1? как можно узнать длину файла в этом случае?
Никак.
Re[3]: DownloadManager на Java
От: Blazkowicz Россия  
Дата: 26.07.07 10:26
Оценка:
Здравствуйте, wirt, Вы писали:


W>ошибку понял. Хочу использовать Channels, но не знаю как его инстанциировать от InputStream полученногго urlConnection.getInputStream()

Вообще я это в свете FileChannel говорил. Ты как-то не слидишь на какие вопросы дают ответы.

W>>>для записи
RandomAccessFile.seek()
.

B>>ОДин экземпляр или разные?
W>Пробовал и так и так. Логично что должны быть разные...
Почему это логично?

W>Заменил на N instance-oв FileChannel, Это решение будет Thread Safe? Или надо блоки синхронизации добавлять?

Надо доку по FileChannel почитать. Поискать в ней по слову Thread.
Re[3]: DownloadManager на Java
От: ynix  
Дата: 26.07.07 10:42
Оценка:
W>Т.е. читать их в ByteArrayStream(?), а потом как только заполнятся на N скидывать на диск?
consider PipedOutputStream
Re[4]: DownloadManager на Java
От: wirt  
Дата: 26.07.07 11:17
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

B>Здравствуйте, wirt, Вы писали:



W>>ошибку понял. Хочу использовать Channels, но не знаю как его инстанциировать от InputStream полученногго urlConnection.getInputStream()

B>Вообще я это в свете FileChannel говорил. Ты как-то не слидишь на какие вопросы дают ответы.

W>>>>для записи
RandomAccessFile.seek()
.

B>>>ОДин экземпляр или разные?
W>>Пробовал и так и так. Логично что должны быть разные...
B>Почему это логично?

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

W>>Заменил на N instance-oв FileChannel, Это решение будет Thread Safe? Или надо блоки синхронизации добавлять?

B>Надо доку по FileChannel почитать. Поискать в ней по слову Thread.
нашел
Re[5]: DownloadManager на Java
От: Blazkowicz Россия  
Дата: 26.07.07 11:22
Оценка:
Здравствуйте, wirt, Вы писали:

W>>>>>для записи
RandomAccessFile.seek()
.

B>>>>ОДин экземпляр или разные?
W>>>Пробовал и так и так. Логично что должны быть разные...
B>>Почему это логично?
W>потому что первый поток seek сделает в одно место файла, а второй в другое и т.д., и тогда все будут писать в одно место

А когда ты пишешь одним экземпляром, он лок на что накладывает? На весь файл небось?
Re: DownloadManager на Java
От: maslyak  
Дата: 02.08.07 01:15
Оценка:
кое-чего тоже нашел

/* DownloadConnection.java
 *
 * $Id: DownloadConnection.java 609 2004-03-01 01:07:24Z enz $
 * $Source: /tmp/irate/irate/irate/download/DownloadConnection.java,v $
 */

package irate.download;

import java.io.*;
import java.net.*;

/** Wrapper class around URLConnection for downloading with timeout.
 *  This class can be used similar to URLConnection, but allows
 *  to specify a timeout value on operations.
 *  If a timeout happens once, all future function calls will
 *  immediately timeout.
 */
public class DownloadConnection {

  public static class ResumeNotSupportedException extends IOException {
  }

  public static class TimeoutException extends IOException {
  }

  public DownloadConnection(URL url) {
    this.url = url;
  }

  /** Close the communication link.
   *  @param timeout the timeout in milliseconds
   *  @exception TimeoutException on timeout
   *  @exception IOException passed from InputStream.close()
   */
  public void close(long timeout) throws IOException {
    if (isTimedOut)
      throw new TimeoutException();
    IOOperation operation = new IOOperation() {
        protected void runIOOperation() throws IOException {
          connection.getInputStream().close();
        };
      };
    runOrTimeout(operation, timeout);
  }
  
  /** Open a communication link.
   *  @param continueOffset offset for resuming a download, set to 0
   *  to download from the beginning
   *  @param timeout the timeout in milliseconds
   *  @exception TimeoutException on timeout
   *  @exception ResumeNotSupportedException if server does not support
   *  resuming a download
   *  @exception IOException passed from URLConnection.openConnection or
   *  URLConnection.connect
   */
  public void connect(long continueOffset, long timeout) throws IOException {
    if (isTimedOut)
      throw new TimeoutException();
    this.continueOffset = continueOffset;
    IOOperation operation = new IOOperation() {
        protected void runIOOperation() throws IOException {
          connection = url.openConnection();
          DownloadConnection downloadConnection = DownloadConnection.this;
          if (downloadConnection.continueOffset > 0) {
            String range = "bytes=" + downloadConnection.continueOffset + "-";
            connection.setRequestProperty("Range", range);
          }
          connection.connect();
        };
      };
    runOrTimeout(operation, timeout);
    if (continueOffset > 0)
      if (connection.getHeaderField("Content-Range") == null)
        throw new ResumeNotSupportedException();
  }

  /** Get the content length.
   *  @return the content length, or -1 if the length is not known.
   */
  public int getContentLength() {
    if (connection == null)
      return -1;
    return connection.getContentLength();
  }

  /** Get the content type.
   *  @return the content type, or null if the type is not known.
   */
  public String getContentType() {
    if (connection == null)
      return null;
    return connection.getContentType();
  }

  /** Read from the connection.
   *  Internally this function uses it's own buffer to avoid that
   *  the passed in buffer is modified after a timeout.
   *  The internal buffer will be allocated on the first call of the
   *  function or every time the size of the passed in buffer changes.
   *  @param buffer the buffer to read into
   *  @param timeout the timeout in milliseconds
   *  @exception TimeoutException on timeout
   *  @exception IOException passed from InputStream.read
   *  @return the number of bytes read.
   */
  public int read(byte[] buffer, long timeout) throws IOException {
    if (isTimedOut)
      throw new TimeoutException();
    if (this.buffer == null || this.buffer.length != buffer.length)
      this.buffer = new byte[buffer.length];
    IOOperation operation = new IOOperation() {
        protected void runIOOperation() throws IOException {
          DownloadConnection downloadConnection = DownloadConnection.this;
          result = connection.getInputStream().read(downloadConnection.buffer);
        };
      };
    runOrTimeout(operation, timeout);
    if (result > 0)
      System.arraycopy(this.buffer, 0, buffer, 0, result);
    return result;
  }

  private boolean isTimedOut;

  private int result;

  private long continueOffset;

  private byte[] buffer;

  private URL url;

  private URLConnection connection;

  private abstract class IOOperation implements Runnable {

    public synchronized boolean isFinished() {
      return finished;
    }
    
    public synchronized IOException getException() {
      return exception;
    }
    
    public void run() {
      try {
        runIOOperation();
        setFinished();
      }
      catch (IOException exception) {
        setException(exception);
      }
    }        
    
    protected abstract void runIOOperation() throws IOException;

    private boolean finished;
    
    private IOException exception;

    private synchronized void setFinished() {
      finished = true;
    }
    
    private synchronized void setException(IOException exception) {
      this.exception = exception;
    }
  };

  private void runOrTimeout(IOOperation operation,
                            long timeout) throws IOException {
    Thread thread = new Thread(operation);
    thread.start();
    try {      
      thread.join(timeout);
    }
    catch (InterruptedException exception) {
      // Shouldn't happen
      exception.printStackTrace();
    }
    if (! operation.isFinished()) {
      isTimedOut = true;
      IOException exception = operation.getException();
      if (exception != null)
        throw exception;
      else
        throw new TimeoutException();
    }
  }

  /** For testing.
   *  Connects to an URL and prints the received bytes to System.out.
   *  @param argv[0] the URL
   *  @param argv[1] the byte offset to start downloading from
   *  @param argv[2] the number of bytes to download
   *  @param argv[3] the timeout in milliseconds
   */
  public static void main(String argv[]) {
    try {
      if (argv.length != 4) {
        System.err.println("Usage: url offset length timeout");
        return;
      }
      URL url = new URL(argv[0]);
      long offset = Long.parseLong(argv[1]);
      int length = Integer.parseInt(argv[2]);
      long timeout = Long.parseLong(argv[3]);
      DownloadConnection ct = new DownloadConnection(url);
      ct.connect(offset, timeout);
      byte[] buffer = new byte[length];
      int n = ct.read(buffer, timeout);
      System.out.println("Bytes read: " + n);
      if (n > 0) {
        System.out.write(buffer, 0, n);
        System.out.println();
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}


немного потестировал в main'е. Произвольный файл с одного раза почему-то сохраняется примерно не более 50кб, то есть какую-нибудь ссылку ему даю на 10 Мб файл а он мне качает 1-50кб и удачно завершается
Re[2]: DownloadManager на Java
От: maslyak  
Дата: 02.08.07 01:19
Оценка:
оффтоп, может кто-нибудь подскажет консольные утилиты под *nix типа wget, которые могли бы качать с произвольной позиции, а то wget для продолжения требует чтобы весь предыдущий кусок существовал?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.