淘先锋技术网

首页 1 2 3 4 5 6 7

多线程下载

原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源

单线程下载:从输入流第0个字节开始读取,读取到最后一个字节,把读取到的数据写到本地文件中,
写的时候也要从文件的第0个位置开始写,写到最后一个位置

多线程的计算:

每个线程预下载的大小:  size = 总的大小/线程的数量  (注意,最后一个问题)
                     size = 10 / 3
id=0线程:  0 -- 2
id=1线程:  3 -- 5
id=2线程:     6 -- 9   (多余的大小可以给最后一个线程    )
开始位置:   start = 线程编号 * 每个线程预下载的大小 
            start = id * size
结束位置:   end =(id+1)*size -1 
最后一个线程结束位置:总的大小-1 
                    length-1

(使用Java项目进行多线程下载测试,因android比较麻烦,
Java中搞定移植到android中即可,测试下载的时候最好使用可执行程序,
比如.exe程序,这样可以知道是否可以真成功)

确定每条线程下载多少数据

  • 发送http请求至下载地址

    String path = "http://192.168.1.13:8080/QQ.exe";        
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setReadTimeout(5000);
    conn.setConnectTimeout(5000);
    conn.setRequestMethod("GET");                   
    
  • 获取文件总长度,然后创建长度一致的临时文件

    if(conn.getResponseCode() == 200){
        //获得服务器流中数据的长度
        int length = conn.getContentLength();
        //创建一个临时文件存储下载的数据
        RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
        //设置临时文件的大小
        raf.setLength(length);
        raf.close();
    
  • 确定线程下载多少数据

        //计算每个线程下载多少数据
        int blockSize = length / THREAD_COUNT;
    

计算每条线程下载数据的开始位置和结束位置

    for(int id = 1; id <= 3; id++){
        //计算每个线程下载数据的开始位置和结束位置
        int startIndex = (id - 1) * blockSize;
        int endIndex = id * blockSize - 1;
        if(id == THREAD_COUNT){
            endIndex = length;
        }

        //开启线程,按照计算出来的开始结束位置开始下载数据
        new DownLoadThread(startIndex, endIndex, id).start();
    }

再次发送请求至下载地址,请求开始位置至结束位置的数据

    String path = "http://192.168.1.13:8080/QQ.exe";

    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setReadTimeout(5000);
    conn.setConnectTimeout(5000);
    conn.setRequestMethod("GET");

    //向服务器请求部分数据
    conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
    conn.connect();

* 下载请求到的数据,存放至临时文件中

    if(conn.getResponseCode() == 206){
        InputStream is = conn.getInputStream();
        RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
        //指定从哪个位置开始存放数据
        raf.seek(startIndex);
        byte[] b = new byte[1024];
        int len;
        while((len = is.read(b)) != -1){
            raf.write(b, 0, len);
        }
        raf.close();
    }

核心代码

    public class MultiDownload {

static int ThreadCount = 3;
static int finishedThread = 0;
//确定下载地址
static String path = "http://192.168.1.13:8080/QQ.exe";
public static void main(String[] args) {

    //发送get请求,请求这个地址的资源
    try {
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);

        if(conn.getResponseCode() == 200){
            //拿到所请求资源文件的长度
            int length = conn.getContentLength();

            File file = new File("local_qq.exe");
            //生成临时文件--占用磁盘空间--可以防止磁盘空间不够用
            RandomAccessFile raf = new RandomAccessFile(file, "rwd");
            //设置临时文件的大小
            raf.setLength(length);
            raf.close();
            //计算出每个线程应该下载多少字节
            int size = length / ThreadCount;

            for (int i = 0; i < ThreadCount; i++) {
                //计算线程下载的开始位置和结束位置
                int startIndex = i * size;
                int endIndex = (i + 1) * size - 1;
                //如果是最后一个线程,那么结束位置写死
                if(i == ThreadCount - 1){
                    endIndex = length - 1;
                }
//                  System.out.println("线程" + i + "的下载区间是:" + startIndex + "---" + endIndex);
                    new DownLoadThread(startIndex, endIndex, i).start();
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


}
class DownLoadThread extends Thread{
    int startIndex; //下载的开始位置
    int endIndex;   //下载的结束位置
    int threadId;   //下载的线程Id

public DownLoadThread(int startIndex, int endIndex, int threadId) {
    super();
    this.startIndex = startIndex;
    this.endIndex = endIndex;
    this.threadId = threadId;
}

@Override
public void run() {
    //再次发送http请求,下载原文件
    try {
        File progressFile = new File(threadId + ".txt");
        //判断进度临时文件是否存在
        if(progressFile.exists()){
            FileInputStream fis = new FileInputStream(progressFile);
            BufferedReader br = new BufferedReader(new InputStreamReader(fis));
            //从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
            startIndex += Integer.parseInt(br.readLine());
            fis.close();
        }
        System.out.println("线程" + threadId + "的下载区间是:" + startIndex + "---" + endIndex);
        HttpURLConnection conn;
        URL url = new URL(MultiDownload.path);
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);
        //设置本次http请求所请求的数据的区间
        conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);

        //请求部分数据,相应码是206
        if(conn.getResponseCode() == 206){
            //流里此时只有1/3原文件的数据
            InputStream is = conn.getInputStream();
            byte[] b = new byte[1024];
            int len = 0;
            int total = 0;
            //拿到临时文件的输出流
            File file = new File("local_qq.exe");
            RandomAccessFile raf = new RandomAccessFile(file, "rwd");
            //把文件的写入位置移动至startIndex
            raf.seek(startIndex);
            while((len = is.read(b)) != -1){
                //每次读取流里数据之后,同步把数据写入临时文件
                raf.write(b, 0, len);
                total += len;
//  System.out.println("线程" + threadId + "下载了" + total);

                //生成一个专门用来记录下载进度的临时文件
                RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
                //每次读取流里数据之后,同步把当前线程下载的总进度写入进度临时文件中
                progressRaf.write((total + "").getBytes());
                progressRaf.close();
            }
            System.out.println("线程" + threadId + "下载完毕-------------------");
            raf.close();

            MultiDownload.finishedThread++;
            synchronized (MultiDownload.path) {
                if(MultiDownload.finishedThread == MultiDownload.ThreadCount){
                    for (int i = 0; i < MultiDownload.ThreadCount; i++) {
                        File f = new File(i + ".txt");
                        f.delete();
                    }
                    MultiDownload.finishedThread = 0;
                }
            }

        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
}