Как бы я делал. В файл пишет только одна нить. Файл принимают по сети несколько других нитей. Периодически эти "приемники" обращатся к файловому писателю с тем, чтобы скинуть свои данные на диск. Эти "обращения" синхронизированные, естественно.
Здравствуйте, wirt, Вы писали:
W>Стоит задача написать некое подобие DownloadManager'a на Java. W>Т.е. на вход подается некий удаленный файл, который мы читаем в несколько потоков и записываем на локальный диск. W>Технические сложности возникли с чтением файла в несколько потоков и соответственно с записью файла в несколько потоков...
ИМХО, до технических сложностей вы еще не дошли. Вам нужно для начала понять, что за задача перед вами стоит.
Начнем с того, что в протоколе HTTP нет понятия файла. Соответственно, не понятно, по какому протоколу вы этот удаленный файл читаете. Далее, не понятно, можно ли этот удаленный файл читать частями и есть ли от этого хоть какая-то польза. Далее, есть ли возможность читать файл с середины, если вы уже прочитали половину, но потеряли связь?
Запись на диск тут самая простая подзадача, я вам скажу.
И, прежде чем мудрить, стоит понять, зачем вы этот DownloadManager пишете, какая у него задача в терминах пользователя?
Стоит задача написать некое подобие DownloadManager'a на Java.
Т.е. на вход подается некий удаленный файл, который мы читаем в несколько потоков и записываем на локальный диск.
Технические сложности возникли с чтением файла в несколько потоков и соответственно с записью файла в несколько потоков...
Как я делаю:
разбиваю файл на N частей, каждая часть читается с определенной позиции и пишется в соответственную позицию отдельным потоком.
Для чтения с определенного места использую
InputStream.mark()
, для записи
RandomAccessFile.seek()
.
Но в результате выполнения получаю фантомные IOExceptions Write error. Возможно это результат проблемы с синхронизацией.
Вопрос, поддерживает ли Java многопоточное чтение с одного InputStream (markable()==true), и поддерживается многопоточная запись в RandomAccessFile? Какие есть другие альтернативы и\или возможные причины проблемы?
Здравствуйте, 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 например.
Спасибо за ответы.
W>>Т.е. на вход подается некий удаленный файл, который мы читаем в несколько потоков и записываем на локальный диск. B>Надо таки конкретизировать что за "удаленный файл". Очень не стоит абстрагироваться от такой вещи как протокол. Потому как для многих протоколов понятие "читаем в несколько потоков" может оказатся не приемлимым.
Для начала пусть будет файн на Http сервере.
W>>Для чтения с определенного места использую
InputStream.mark()
, B>Кто надоумил? Пробовал прочитать внимательно доку к этому методу и обдумать?
ошибку понял. Хочу использовать Channels, но не знаю как его инстанциировать от InputStream полученногго urlConnection.getInputStream()
W>>для записи
RandomAccessFile.seek()
. B>ОДин экземпляр или разные?
Пробовал и так и так. Логично что должны быть разные... Заменил на N instance-oв FileChannel, Это решение будет Thread Safe? Или надо блоки синхронизации добавлять?
Здравствуйте, C0s, Вы писали:
C0s>Здравствуйте, wirt, Вы писали:
W>>Стоит задача написать некое подобие DownloadManager'a на Java.
C0s>за нас уже написали: Sun Download Manager
Здравствуйте, ynix, Вы писали:
Y>Как бы я делал. В файл пишет только одна нить. Файл принимают по сети несколько других нитей. Периодически эти "приемники" обращатся к файловому писателю с тем, чтобы скинуть свои данные на диск. Эти "обращения" синхронизированные, естественно.
Т.е. читать их в ByteArrayStream(?), а потом как только заполнятся на N скидывать на диск?
Здравствуйте, 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? как можно узнать длину файла в этом случае?
Здравствуйте, wirt, Вы писали:
W>>>>Стоит задача написать некое подобие DownloadManager'a на Java.
W>правильно ли я понимаю, что этим параметром можно регулировать с какой позиции читать? где можно подробно прочитать про этот и другие параметры? все ли веб серверы его поддерживают?
Только так и можно делать. Читайте спецификацию HTTP
W>Второй вопрос: W>как быть в том случае, когда contentLength == -1? как можно узнать длину файла в этом случае?
Здравствуйте, wirt, Вы писали:
W>правильно ли я понимаю, что этим параметром можно регулировать с какой позиции читать? где можно подробно прочитать про этот и другие параметры?
HTTP Headers RFC.
W>все ли веб серверы его поддерживают?
Нет.
W>Второй вопрос: W>как быть в том случае, когда contentLength == -1? как можно узнать длину файла в этом случае?
Никак.
W>ошибку понял. Хочу использовать Channels, но не знаю как его инстанциировать от InputStream полученногго urlConnection.getInputStream()
Вообще я это в свете FileChannel говорил. Ты как-то не слидишь на какие вопросы дают ответы.
W>>>для записи
RandomAccessFile.seek()
. B>>ОДин экземпляр или разные? W>Пробовал и так и так. Логично что должны быть разные...
Почему это логично?
W>Заменил на N instance-oв FileChannel, Это решение будет Thread Safe? Или надо блоки синхронизации добавлять?
Надо доку по FileChannel почитать. Поискать в ней по слову Thread.
Здравствуйте, 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.
нашел
. B>>>>ОДин экземпляр или разные? W>>>Пробовал и так и так. Логично что должны быть разные... B>>Почему это логично? W>потому что первый поток seek сделает в одно место файла, а второй в другое и т.д., и тогда все будут писать в одно место
А когда ты пишешь одним экземпляром, он лок на что накладывает? На весь файл небось?
/* 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кб и удачно завершается
оффтоп, может кто-нибудь подскажет консольные утилиты под *nix типа wget, которые могли бы качать с произвольной позиции, а то wget для продолжения требует чтобы весь предыдущий кусок существовал?