Java
常见的FTP,HTTP,BT等协议都是TCP的,但是现在流行的utorrent却基于UDP实现了自己UTP协议(UDP+拥塞控制),不管使用什么协议,站在I/O的角度来说,限速的控制思路都是一样的。
思路很简单,如下:
1.假设下载或者上传速度上限是m (KB/s),那么发送一个固定的字节数据(假设是n字节)的时间花费是:n/m;
2.假设现在要发送n字节的数据,那么理论所需的时间应该是n/m,而在实际情况下,发送n字节的数据只花费了t秒,那么发送该发送线程就应该睡眠n/m-t秒,这样就基本实现了速度的控制。
代码以TCP为例

速度控制

  1. public class BandwidthLimiter {
  2. /* KB */
  3. private static Long KB = 1024l;
  4. /* The smallest count chunk length in bytes */
  5. private static Long CHUNK_LENGTH = 1024l;
  6. /* How many bytes will be sent or receive */
  7. private int bytesWillBeSentOrReceive = 0;
  8. /* When the last piece was sent or receive */
  9. private long lastPieceSentOrReceiveTick = System.nanoTime();
  10. /* Default rate is 1024KB/s */
  11. private int maxRate = 1024;
  12. /* Time cost for sending CHUNK_LENGTH bytes in nanoseconds */
  13. private long timeCostPerChunk = (1000000000l * CHUNK_LENGTH)
  14. / (this.maxRate * KB);
  15. /**
  16. * Initialize a BandwidthLimiter object with a certain rate.
  17. *
  18. * @param maxRate
  19. * the download or upload speed in KBytes
  20. */
  21. public BandwidthLimiter(int maxRate) {
  22. this.setMaxRate(maxRate);
  23. }
  24. /**
  25. * Set the max upload or download rate in KB/s. maxRate must be grater than
  26. * 0. If maxRate is zero, it means there is no bandwidth limit.
  27. *
  28. * @param maxRate
  29. * If maxRate is zero, it means there is no bandwidth limit.
  30. * @throws IllegalArgumentException
  31. */
  32. public synchronized void setMaxRate(int maxRate)
  33. throws IllegalArgumentException {
  34. if (maxRate < 0) {
  35. throw new IllegalArgumentException("maxRate can not less than 0");
  36. }
  37. this.maxRate = maxRate < 0 ? 0 : maxRate;
  38. if (maxRate == 0)
  39. this.timeCostPerChunk = 0;
  40. else
  41. this.timeCostPerChunk = (1000000000l * CHUNK_LENGTH)
  42. / (this.maxRate * KB);
  43. }
  44. /**
  45. * Next 1 byte should do bandwidth limit.
  46. */
  47. public synchronized void limitNextBytes() {
  48. this.limitNextBytes(1);
  49. }
  50. /**
  51. * Next len bytes should do bandwidth limit
  52. *
  53. * @param len
  54. */
  55. public synchronized void limitNextBytes(int len) {
  56. this.bytesWillBeSentOrReceive += len;
  57. /* We have sent CHUNK_LENGTH bytes */
  58. while (this.bytesWillBeSentOrReceive > CHUNK_LENGTH) {
  59. long nowTick = System.nanoTime();
  60. long missedTime = this.timeCostPerChunk
  61. - (nowTick - this.lastPieceSentOrReceiveTick);
  62. if (missedTime > 0) {
  63. try {
  64. Thread.sleep(missedTime / 1000000,
  65. (int) (missedTime % 1000000));
  66. } catch (InterruptedException e) {
  67. e.printStackTrace();
  68. }
  69. }
  70. this.bytesWillBeSentOrReceive -= CHUNK_LENGTH;
  71. this.lastPieceSentOrReceiveTick = nowTick
  72. + (missedTime > 0 ? missedTime : 0);
  73. }
  74. }
  75. }

下载控制

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. public class DownloadLimiter extends InputStream {
  4. private InputStream is = null;
  5. private BandwidthLimiter bandwidthLimiter = null;
  6. public DownloadLimiter(InputStream is, BandwidthLimiter bandwidthLimiter)
  7. {
  8. this.is = is;
  9. this.bandwidthLimiter = bandwidthLimiter;
  10. }
  11. @Override
  12. public int read() throws IOException {
  13. if(this.bandwidthLimiter != null)
  14. this.bandwidthLimiter.limitNextBytes();
  15. return this.is.read();
  16. }
  17. public int read(byte b[], int off, int len) throws IOException
  18. {
  19. if (bandwidthLimiter != null)
  20. bandwidthLimiter.limitNextBytes(len);
  21. return this.is.read(b, off, len);
  22. }
  23. }

上传控制

  1. import java.io.IOException;
  2. import java.io.OutputStream;
  3. public class UploadLimiter extends OutputStream {
  4. private OutputStream os = null;
  5. private BandwidthLimiter bandwidthLimiter = null;
  6. public UploadLimiter(OutputStream os, BandwidthLimiter bandwidthLimiter)
  7. {
  8. this.os = os;
  9. this.bandwidthLimiter = bandwidthLimiter;
  10. }
  11. @Override
  12. public void write(int b) throws IOException {
  13. if (bandwidthLimiter != null)
  14. bandwidthLimiter.limitNextBytes();
  15. this.os.write(b);
  16. }
  17. public void write(byte[] b, int off, int len) throws IOException {
  18. if (bandwidthLimiter != null)
  19. bandwidthLimiter.limitNextBytes(len);
  20. this.os.write(b, off, len);
  21. }
  22. }

对于一个TCP socket

  1. ServerSocket socket = new ServerSocket();
  2. //其它初始化略
  3. //从socket中以一定的速率读数据
  4. DownloadLimiter dl = new DownloadLimiter(socket.getInputStream(), new BandwidthLimiter(6250));
  5. is = new DataInputStream(dl);
  6. //读数据
  7. int len = is.readInt();
  8. ByteBuffer buffer = ByteBuffer.allocate(4 + len);
  9. buffer.putInt(len);
  10. is.readFully(buffer.array(), 4, buffer.remaining());
  11. //以一定的速率写数据到socket
  12. UploadLimiter ul = new UploadLimiter(socket.getOutputStream(), new BandwidthLimiter(6250));
  13. ul.write();

在多线程环境下也可以使用上述的方法。最后附图是任务管理器的网络利用率图6250KB/s(也就是50000kb/s,附图中网络利用率也在5%左右,所以应该这个做法还算准确)
Java网络编程中控制上传和下载速度 - 图1