我使用以下代码来限制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();
}
}
}
成功限制了下载速度,但是出现了一个新问题。当下载正在进行中,并且我断开了与互联网的连接时,下载不会结束,并且会持续一段时间。当我断开Internet连接时,将花费10秒钟以上的时间来抛出java.net.SocketTimeoutException
异常。我不太了解后台会发生什么。
为什么会出现此问题?
答案 0 :(得分:3)
您显然希望限制客户端的下载速度,并且还希望客户端对关闭的连接做出立即的响应。
AFAIK,这是不可能的……没有一些妥协。
问题在于,客户端应用程序可以通过执行read
操作来以 only 的方式检测到连接已关闭。该读取将传递数据。但是,如果您已经达到当前期限的上限,那么读取的数据将使您超过限制。
这里有两个想法:
如果您在短时间内“集成”下载速率(例如,每秒1 KB相对于每10秒10 KB),则可以缩短sleep
调用的时间。
当您接近目标下载速率时,您可能会退而去进行微小的读取(例如1字节)和小的睡眠。
不幸的是,这两种方法在客户端都效率不高(更多的syscalls),但这是您希望应用程序快速检测到连接关闭时必须支付的费用。
在评论中您说:
我希望在互联网连接被禁用后立即重置连接。
我不这么认为。通常,客户端协议栈会传递从网络接收到的所有未完成的数据,然后告诉应用程序代码正在读取的连接已关闭。
答案 1 :(得分:2)
您的速率限制实际上并不像您认为的那样起作用,因为数据实际上不是按字节发送,而是按数据包发送。这些数据包已缓冲,您观察到的内容(下载会在没有连接的情况下继续进行)只是您的流在读取缓冲区。一旦到达缓冲区末尾,它将等待5秒钟才抛出超时(因为这是您配置的时间)。
您将速率设置为8 kB / s,并且正常的数据包大小通常约为1 kB,并且可以上升到64 kB,因此仍然需要8秒才能读取相同的数据包。另外,可能已经发送和缓冲了多个数据包。还有一个接收缓冲区this buffer can be as small as 8 - 32 kB up to several MB。所以说真的,您只是从缓冲区读取数据。
[编辑]
为了澄清,您做对了。平均而言,费率将受限于您指定的费率。服务器将发送一堆数据,然后等待客户端清空其缓冲区足以接收更多数据。