Как программно ограничить скорость загрузки?
Я использую следующий код, чтобы ограничить скорость загрузки файла в Java:
package org;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
class MainClass {
public static void main(String[] args) {
download("https://speed.hetzner.de/100MB.bin");
}
public static void download(String link) {
try {
URL url = new URL(link);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(5000);
con.setReadTimeout(5000);
InputStream is = con.getInputStream();
CustomInputStream inputStream = new CustomInputStream(is);
byte[] buffer = new byte[2024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
System.out.println("downloaded : " + len);
//save file
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static class CustomInputStream extends InputStream {
private static final int MAX_SPEED = 8 * 1024;
private final long ONE_SECOND = 1000;
private long downloadedWhithinOneSecond = 0L;
private long lastTime = System.currentTimeMillis();
private InputStream inputStream;
public CustomInputStream(InputStream inputStream) {
this.inputStream = inputStream;
lastTime = System.currentTimeMillis();
}
@Override
public int read() throws IOException {
long currentTime;
if (downloadedWhithinOneSecond >= MAX_SPEED
&& (((currentTime = System.currentTimeMillis()) - lastTime) < ONE_SECOND)) {
try {
Thread.sleep(ONE_SECOND - (currentTime - lastTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
downloadedWhithinOneSecond = 0;
lastTime = System.currentTimeMillis();
}
int res = inputStream.read();
if (res >= 0) {
downloadedWhithinOneSecond++;
}
return res;
}
@Override
public int available() throws IOException {
return inputStream.available();
}
@Override
public void close() throws IOException {
inputStream.close();
}
}
}
Скорость загрузки успешно ограничена, но возникает новая проблема. Когда загрузка продолжается, и я отключаюсь от Интернета, загрузка не заканчивается и продолжается некоторое время. Когда я отключаю интернет-соединение, требуется более 10 секунд, чтобы java.net.SocketTimeoutException
исключение. Я не очень понимаю, что происходит в фоновом режиме. почему возникает эта проблема? Что я могу сделать, чтобы решить эту проблему? Огромное спасибо.
Я заранее извиняюсь, если грамматика моих предложений неверна. Потому что я плохо говорю по-английски.
2 ответа
Ваш предел скорости на самом деле не работает так, как вы думаете, потому что данные на самом деле отправляются не по байтам, а в пакетах. Эти пакеты буферизуются, и то, что вы наблюдаете (загрузка продолжается без подключения), является просто вашим потоком, читающим буфер. Как только он достигает конца вашего буфера, он ждет 5 секунд, прежде чем истечет время ожидания (потому что это то, что вы настроили).
Вы устанавливаете скорость 8 кБ / с, и нормальный размер пакета обычно составляет около 1 кБ и может достигать 64 кБ, поэтому будет 8 секунд, когда вы все еще читаете тот же пакет. Кроме того, возможно, что несколько пакетов уже были отправлены и буферизованы. Существует также приемный буфер, этот буфер может быть размером от 8 - 32 кБ до нескольких МБ. Так что на самом деле вы просто читаете из буфера.
[РЕДАКТИРОВАТЬ]
Просто чтобы уточнить, вы делаете правильные вещи. В среднем скорость будет ограничена тем, что вы укажете. Сервер отправит кучу данных, а затем подождет, пока клиент не освободит свой буфер достаточно, чтобы получить больше данных.
Очевидно, вы хотите ограничить скорость загрузки на стороне клиента, и вы также хотите, чтобы клиент немедленно реагировал на закрытие соединения.
AFAIK, это невозможно... без некоторых компромиссов.
Проблема заключается в том, что единственный способ, которым клиентское приложение может обнаружить, что соединение закрыто, это выполнить read
операция. Это чтение собирается доставить данные. Но если вы уже достигли своего предела за текущий период, то это чтение приведет вас к превышению лимита.
Вот пара идей:
Если вы "интегрируете" скорость загрузки в течение короткого периода времени (например, 1 КБ каждую секунду против 10 КБ каждые 10 секунд), то вы можете сократить продолжительность
sleep
звонки.Когда вы приблизитесь к целевой скорости загрузки, вы можете вернуться к крошечному (например, 1 байт) чтению и маленькому сну.
К сожалению, оба из них будут неэффективными на стороне клиента (больше системных вызовов), но это стоимость, которую вы должны заплатить, если вы хотите, чтобы ваше приложение быстро обнаруживало закрытие соединения.
В комментарии вы сказали:
Я ожидаю, что соединение будет сброшено, как только интернет отключится.
Я так не думаю. Обычно стек протоколов на стороне клиента будет доставлять все ожидающие данные, полученные из сети, прежде чем сообщать коду приложения о том, что соединение, которое он читает, было закрыто.