1.Java 的屏幕广播(基于UDP),2.多线程下载器

Java 的屏幕广播(基于UDP)

Java的屏幕广播,是基于UDP协议的,user datagram protocal 用户数据报协议,无连接,无顺序,不安全,但是作为发送实时数据还是十分常用的。

整个难点在于要字节制定协议,由于UDP的一个包最大不能超过64K,而一帧屏幕截图(1366*768)是肯定超过64K的,所以我们需要对所截出来的image进行分割发送。
假设我们将一张屏幕截图分割成若干个包发送出去,要构成屏幕广播,就需要不断的截图发包,接收端就肯定需要知道哪几个包是同一张图片,而在这几个包中哪个包是图片的哪一部分,便于恢复成一张完整的图片。

如此就需要一个协议来规定各个包之间的关系。

在下面的程序中,每张图片之间我用时间戳来区别,图片中分割的各个部分用编号来确定,还要加上每张图片所分割的数量,就构造了一个数据报包。

首先的代码是一个工具类,将byte转换成long、int,还有反转

package Boardcast;

 * 用于存放byte转换成long,int的工具代码
public class Utils {

    public static byte[] long2Byte(long number) {
        byte[] b = new byte[8];
        for(int i=0; i<8; i++) {
            b[i] = (byte) (number>>((8-i-1)*8));
        }
        return b;
    }
    
     * 从offset开始往后的8个字节转换为long数据
    public static long byte2Long(byte[] b, int offset) {
        long end = 0;
        for(int i=0; i<8; i++) {
            end = end | ((long)(b[i+offset] & 0xff)<<((8-i-1)*8));
             * 其中的long类型转换一定要加上,如果没加上结果就成了int类型
        }
        return end;
    }

    public static byte[] int2Byte(int number) {
        byte[] b = new byte[4];
        b[0] = (byte) (number >> 24);
        b[1] = (byte) (number >> 16);
        b[2] = (byte) (number >> 8);
        b[3] = (byte)number;
        return b;
    }

    public static int byte2Int(byte[] b, int offset) {
        int i3 = (b[0+offset] & 0xFF)<< 24;
        int i2 = (b[1+offset] & 0xFF)<< 16;
        int i1 = (b[2+offset] & 0xFF)<< 8;
        int i0 =  b[3+offset] & 0xFF;
        return i3 | i2 | i1 | i0;
    }
}

下面是广播者,发送截屏后过压缩,将压缩后的数据分割后进行发送

package Boardcast;

import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.imageio.ImageIO;

 * 广播者,屏幕截图后转换成byte[]格式,过压缩,分割成一个个60K左右的小包发送出去
 * 对于同一帧的图片分割出来的小包,有着相同的时间戳,同样的包数量,不同的编号,用于区分

public class Boardcaster {
    private DatagramSocket socket;
    private Robot robot;
    private Rectangle rect;
    
    public static void main(String[] args) {
        Boardcaster bc = new Boardcaster();
        System.out.println("开始发送数据。。。。");
        bc.start();
    }
    
    public void start() {
        try {
            socket = new DatagramSocket(8888);
             * 实例化一个Robot,用于抓图
            robot = new Robot();
             * 设置所抓图片的位置,长宽
            rect = new Rectangle(0, 0, 1366, 768);
             
            while(true) {
                popDatagramPacket(socket);
                System.out.println("发送一帧图片");
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
     * 抓图
    public BufferedImage getPrintScreen() {
        BufferedImage image = robot.createScreenCapture(rect);
        return image;
    }
    
     * 抓图,压缩,分割发送
    private void popDatagramPacket(DatagramSocket socket) {
        try {
             * 抓图
            BufferedImage image = getPrintScreen();
             * 将图写入baos
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(image, "jpg", baos);
            
            System.out.println("未压缩的byte[]大小 = " + baos.toByteArray().length);
             * 过压缩
            byte[] buffer = compressImage(baos.toByteArray());
            
            System.out.println("过压缩的byte[]大小 = " + buffer.length);
            
             * 分割图片并发送出去
            cutImageByteAndPost(buffer, socket);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
     * 将image的byte[]过压缩,返回压缩后的byte[]
    private byte[] compressImage(byte[] buffer) throws Exception {
         * 新建一个baos,用于存放转压缩后的byte[]数据
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(baos);
        
        ZipEntry entry = new ZipEntry("image.jpg");
         * 放入条目
        zos.putNextEntry(entry);
         * 写入数据
        zos.write(buffer);
        
        zos.close();
        baos.close();
        
        return baos.toByteArray();
    }

     * 将数据的byte[]分割发送出去,同一帧的图片分割成的小包,有着相同的时间戳,同样的包数量,不同的编号
     * 每一个小包,开始8个字节存放时间戳,接下来四个字节存放本帧图片所分割出来的包数量,再放入四个字节的编号
    public void cutImageByteAndPost(byte[] src, DatagramSocket socket) {
        try {
            int len = src.length;
             * 分割的包数量
            int count = len/60/1024;
            if(len > count*60*1024) {
                count++;
            }
            System.out.println("len = " + len + ", count = " + count);
            
            byte[] buffer = new byte[60*1024+8+4+4];
            long time = System.nanoTime();
            
             * 发count数量的包
            for(int i=0; i

接收者,较广播方复杂一些

是用JFrame建立的界面,用于展示收到的屏幕截图
对上面的数据进行反解,取出各个小包中的内容数据,合成一个大的数据byte[],再解压缩成图片数据,最后转换成图片显示再界面中

在对数据报包合成时,使用Map来存储,时间戳作为key,value是TreeMap,每个小包解析为一个PackInfo对象,Integer是存放包的编号,TreeMap本身是有序存放的,所以合成数据时直接取出来合成即可

package Boardcast;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.ZipInputStream;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

 * 接收屏幕广播界面
public class BoradcastScreenClientUI extends JFrame{
    private static final long serialVersionUID = -580341982722536152L;
     * label用于放置image
    private JLabel label;

    public static void main(String[] args) {
        BoradcastScreenClientUI bsClient = new BoradcastScreenClientUI();
    }
    
    public BoradcastScreenClientUI() {
        init();
        getData();
    }
    
     * 初始化面板设置,设置一个label,放置image
    public void init() {
        this.setTitle("屏幕广播");
        this.setBounds(270, 80, 1366, 768);
        
        try {
            BufferedImage image = ImageIO.read(new File("E://TestCase//day20//demo.jpg"));
            ImageIcon icon = new ImageIcon(image);
            label = new JLabel(icon);
            label.setBounds(25, 25, 1366, 768);
        } catch(Exception e) {
            e.printStackTrace();
        }
        this.add(label);
        
         * 监听窗口关闭事件,关闭窗口的同时停止程序
        this.addWindowListener(new WindowAdapter(){
            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println("关闭窗口");
                System.exit(-1);
            }   
        });
        
         * 设置面板可见
        this.setVisible(true);
    }
    
     * 启动一个线程监听端口,接收数据,解析成image并放入面板中显示
    public void getData() {
        Thread t = new Thread() {
            private DatagramSocket receiver;
            @Override
            public void run() {
                try {
                    receiver = new DatagramSocket(9999);
                    byte[] buffer = new byte[64*1024];
                    DatagramPacket pack = new DatagramPacket(buffer, buffer.length);
                    
                    System.out.println("等待接收数据");
                     * 用Map来存放image的各个包数据,Long是接收到数据包的时间戳,Integer是包的编号,
                     * PackInfo中存放了解析出来的包数据、包数量、包编号、时间戳
                    Map> map = null;
                     * key用于存放接收的上一个包数据的时间戳
                    long key = 0;
                    while(true) {
                        receiver.receive(pack);
                        System.out.println("收到一个数据报包 " + pack.getLength());
                         * 将收到的数据包解析成数据对象
                        PackInfo packinfo = parsePackInfo(pack);
                        
                         * 如果接收的的数据时间戳大于之前的包的时间戳,则丢弃之前的map,新建一个map,放入新的数据
                        if(packinfo.getTime() > key) {
                            map = new HashMap>();
                            HashMap value = new HashMap();
                            value.put(packinfo.getNum(), packinfo);
                            map.put(packinfo.getTime(), value);
                             * 更新key的值
                            key = packinfo.getTime();
                        }else if(packinfo.getTime() == key) {  * 如果时间戳相等则将包数据放入map中
                            map.get(key).put(packinfo.getNum(), packinfo);
                             * 检测是否够合成一张图片了,条件是map.value.size等于包的数量
                            checkMap(map);
                        }
                    }
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        };
         * 作为守护线程,并启动
        t.setDaemon(true);
        t.start();
    }
    
     * 解析一个包的数据,并放入对象中返回
    private PackInfo parsePackInfo(DatagramPacket pack) {
         * 新建一个包数据对象存放数据
        PackInfo packinfo = new PackInfo();
        
        byte[] buffer = pack.getData();
        
         * 取出时间戳,0-7的字节
        long time = Utils.byte2Long(buffer, 0);
        packinfo.setTime(time);
        
         * 取出分割数量,8-11的字节
        int count = Utils.byte2Int(buffer, 8);
        packinfo.setCount(count);
        
         * 取出当前编号,12-15的字节
        int num = Utils.byte2Int(buffer, 12);
        packinfo.setNum(num);
        
         * 当前被压缩后的数据
        byte[] data = new byte[pack.getLength()-16];
        System.arraycopy(buffer, 16, data, 0, data.length);
        packinfo.setData(data);
        
        System.out.println(time + ", " + count + ", " + num + ", " + data.length);
        
        return packinfo;
    }
    
     * 检测放入的小包是否等于包的总数,等于则合成一张图片
    public void checkMap(Map> map) throws Exception {
        Iterator>> it = map.entrySet().iterator();
        while(it.hasNext()) {
            Entry> entry = it.next();
            PackInfo packinfo = entry.getValue().entrySet().iterator().next().getValue();
            
             * 检测是否集齐了所有小包,集齐了就合成一帧图片
            if(packinfo.getCount() == entry.getValue().size()) {
                System.out.println("满了,合成图片");
                
                 * 将各个小包合成图片
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                
                for(int i=0; i 
  

启动广播者,不断的在发送数据包
1.Java 的屏幕广播(基于UDP),2.多线程下载器_第1张图片

可以看到面板中的实时屏幕广播了
1.Java 的屏幕广播(基于UDP),2.多线程下载器_第2张图片



多线程下载器

使用java的多线程从服务器下载文件,我的服务器是在本地用tomcat搭建的

可以实现暂停下载和断点续传,断点续传的数据文件是用Properties来处理的。

每个线程下载后都会更新下载的数据文件来保存下载信息,使得在停止下载后可以续传。

package multidownload;

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;

 * 使用多线程从服务器下载文件
public class DownloadUI extends JFrame implements ActionListener {
    private static final long serialVersionUID = 5521199214537722822L;
    public JTextField textUrl;   * 下载的URL输入框
    public JTextField textPath;   * 保存路径输入框
    public JTextField textThreadCount;  * 线程数量
    private JButton buttonStart;
    private JButton buttonStop;
    public JProgressBar bar;     * 总进度条
    public boolean flag = false;  * 是否暂停下载标志位
    public String ProfilePath;    * 下载线程保存的数据文件路径
    public Properties p;    * 下载线程保存的数据文件

    public static void main(String[] args) {
        DownloadUI multiDown = new DownloadUI();
    }

    public DownloadUI() {
        init();
    }

    private void init() {
        this.setTitle("多线程下载");
        this.setBounds(270, 80, 800, 600);
        this.setLayout(null);

        Font fontText = new Font("宋体", Font.ITALIC, 20);
        Font fontLab = new Font("宋体", Font.BOLD, 27);

        JLabel labSrcPath = new JLabel("URL地址");
        labSrcPath.setFont(fontLab);
        labSrcPath.setBounds(30, 30, 120, 60);

        textUrl = new JTextField();
        textUrl.setFont(fontText);
        textUrl.setBounds(150, 30, 400, 60);
        textUrl.setText("http://localhost:8000//demo.avi");

        this.add(labSrcPath);
        this.add(textUrl);

        JLabel labAimPath = new JLabel("保存路径");
        labAimPath.setFont(fontLab);
        labAimPath.setBounds(20, 130, 150, 60);

        textPath = new JTextField();
        textPath.setFont(fontText);
        textPath.setBounds(150, 130, 400, 60);
        textPath.setText("E:\\TestCase\\day21\\demo.avi");

        this.add(labAimPath);
        this.add(textPath);

        JLabel labThreadCount = new JLabel("线程数量");
        labThreadCount.setFont(fontLab);
        labThreadCount.setBounds(20, 230, 150, 60);

        textThreadCount = new JTextField();
        textThreadCount.setFont(fontLab);
        textThreadCount.setBounds(150, 230, 400, 60);
        textThreadCount.setText("3");

         * 开始下载按钮
        buttonStart = new JButton("开始下载");
        buttonStart.setBounds(100, 320, 100, 40);
        buttonStart.addActionListener(this);

         * 停止下载按钮
        buttonStop = new JButton("停止下载");
        buttonStop.setBounds(250, 320, 100, 40);
        buttonStop.addActionListener(this);

        this.add(labThreadCount);
        this.add(textThreadCount);
        this.add(buttonStart);
        this.add(buttonStop);

         * 添加窗口事件处理程序,使用适配器
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println("关闭窗口");
                System.exit(-1);
            }
        });

         * 进度条,作为总量进度条
        bar = new JProgressBar();
        bar.setBounds(50, 380, 700, 50);
        this.add(bar);

        this.setVisible(true);
    }

     * 对按钮的事件监听和处理
    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            Downloader downloader;
            if (e.getSource() == buttonStart) {  * 要判断是新的下载还是断点续传
                System.out.println("点击开始按钮");

                 * 获取面板上的数据
                String url = textUrl.getText();
                String savePath = textPath.getText();
                int count = Integer.parseInt(textThreadCount.getText());
                System.out.println("URL地址:" + url + ", 保存路径:" + savePath + ", 线程数:" + count);
                
                 * 为了可以断点续传,需要存储传输的各个线程节点的信息,线程的开始位置,传输数量等
                 * 构造数据文件的路径,和保存的文件路径在同一个位置,命名的也相同,后缀名为.properties
                ProfilePath = savePath.substring(0, savePath.lastIndexOf(".")) + ".properties";
                
                 * flag是暂停的标志,检测是否是已经开启过线程下载了
                if(flag) {
                    System.out.println("下载已暂停,请点击继续下载");
                }else {
                    File file = new File(ProfilePath);
                    
                     *  1.初始化下载器对象
                    downloader = new Downloader(url, savePath, count, this);
                    
                    if (file.exists()) {  * 断点续传
                        System.out.println("断点续传");
                        
                         * 是断点续传的话就肯定会存在同名的数据文件,读取传输的量并继续下载
                        p = new Properties();
                        p.load(new FileInputStream(ProfilePath));

                         *  2.取得初始化列表对象
                        List lists = downloader.keepDownload();

                        System.out.println("文件总大小:" + downloader.fileSize);
                        
                         *  3.给总进度条设置最大值
                        this.bar.setMaximum(downloader.fileSize);
                        
                         *  4.按照线程数量动态添加进度条
                        List bars = addBar(lists);

                         *  5.开始下载
                        downloader.startDownload(bars);

                    }else {  * 新的下载
                        System.out.println("开始新的下载");
                        
                         *  2.取得初始化列表对象
                        List lists = downloader.prepareDownload();

                         *  3.将基本数据存入一个properties文件中
                        saveDownloadInfo(lists, downloader.fileSize);

                        System.out.println("文件总大小:" + downloader.fileSize);

                         *  4.给进度条设置最大值
                        this.bar.setMaximum(downloader.fileSize);

                         *  5.按照线程数量动态添加进度条
                        List bars = addBar(lists);

                         *  6.开始下载
                        downloader.startDownload(bars);
                    }
                }
            } else if (e.getSource() == buttonStop) {  * 检测是否停止
                System.out.println("点击停止按钮");
                this.flag = !this.flag;
            }
        } catch (Exception e2) {
            e2.printStackTrace();
        }
    }

     * 保存下载文件的下载数据信息
    private void saveDownloadInfo(List lists, int fileSize) {
        try {
            p = new Properties();
             * 设置公共的url,savePath,线程数量,文件总大小
            p.setProperty("Thread.url", lists.get(0).getUrl());
            p.setProperty("Thread.savePath", lists.get(0).getSavePath());
            p.setProperty("Thread.count", lists.size() + "");
            p.setProperty("Thread.fileSize", fileSize + "");
             * 为每一个线程设置基本数据,下载的位置,还需要下载的size大小,下载的总数amount,线程编号index
            for (Download d : lists) {
                p.setProperty("Thread." + d.getIndex() + ".start", d.getStart() + "");
                p.setProperty("Thread." + d.getIndex() + ".size", d.getSize() + "");
                p.setProperty("Thread." + d.getIndex() + ".amount", d.getSize() + "");
                p.setProperty("Thread." + d.getIndex() + ".index", d.getIndex() + "");
            }
            p.store(new FileOutputStream(ProfilePath), "Thread basic information for remember");

            System.out.println("保存基本数据");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("保存初始化数据出错");
        }
    }
    
     * 按照线程个数动态添加进度条
    private List addBar(List lists) {
        List bars = new ArrayList();
        bar.setValue(0);
         * allAmount是为总进度条设置的
        int allAmount = 0;
        for (Download download : lists) {
            JProgressBar b = new JProgressBar();
            b.setBounds(50, 420 + (download.getIndex() + 1) * 30, 700, 25);

            int amount = Integer.parseInt(p.getProperty("Thread." + download.getIndex() + ".amount"));
            b.setMaximum(amount);

            b.setValue(amount - download.getSize());
            allAmount = allAmount + amount - download.getSize();
            this.add(b);
            bars.add(b);
        }
        this.bar.setValue(allAmount);
        this.repaint();
        return bars;
    }
}
package multidownload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JProgressBar;

 * 这是一个下载器,启动下载线程
public class Downloader {
    private String url;       * 下载的url
    private String savePath;  * 本地保存路径
    private int count;        * 线程数量
    private List lists;  * 下载线程数的info列表
    public int fileSize;      * 要下载的文件大小
    private DownloadUI downui;  * 对应的就是DownloadUI传过来的对象
    
     * 初始化
    public Downloader(String url, String savePath, int count, DownloadUI downui) throws Exception {
        this.url = url;
        this.savePath = savePath;
        this.count = count;
        this.downui = downui;
    
        URL u = new URL(url);
        URLConnection conn = u.openConnection();
        fileSize = conn.getContentLength();
    }
    
     * 下载前的初始化工作
    public List prepareDownload() {
        try {
             * 每个线程下载的基本数量,最后一个线程下载的量要另外计算
            int block = fileSize/count;
            
            lists = new ArrayList();
            for(int i=0; i keepDownload(){
        lists = new ArrayList();
        
        int count = Integer.parseInt(downui.p.getProperty("Thread.count"));
        String url = downui.p.getProperty("Thread.url");
        String savePath = downui.p.getProperty("Thread.savePath");
        
        downui.textThreadCount.setText(count+"");
        downui.textUrl.setText(url);
        downui.textPath.setText(savePath);
        
        for(int i=0; i bars) {
        for(Download download : lists) {
            System.out.println(download);
            JProgressBar b = bars.get(download.getIndex());
            new DownloadThread(download, b, downui).start();
        }
    }
}

 * 下载线程,每个线程下载的位置信息从download对象中取得
 * 每个线程操作的Properties对象都是同一个,如果不是同一个就会有数据丢失的风险
class DownloadThread extends Thread{
    private Download download;
    private JProgressBar b;
    private DownloadUI downui;
    public DownloadThread(Download download, JProgressBar b, DownloadUI downui){
        this.download = download;
        this.b = b;
        this.downui = downui;
    }
    
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " download = " + download);
        try {
             * 连接
            URL url = new URL(download.getUrl());
            URLConnection conn = url.openConnection();
             * 设置请求头信息
            conn.setRequestProperty("Range", "bytes=" + download.getStart() + "-" + (download.getStart()+download.getSize()));
            
            System.out.println(Thread.currentThread().getName() + "bytes=" + download.getStart() + "-" + (download.getStart()+download.getSize()));
            
            InputStream in = conn.getInputStream();
            RandomAccessFile raf = new RandomAccessFile(download.getSavePath(), "rw");
            raf.seek(download.getStart());
            
            int len = -1;
            byte[] buffer = new byte[1024];
            
            while((len = in.read(buffer)) != -1) {
                 * 检测是否停止下载,不要使用while(downui.flag);这句来堵塞进程,效果不好
                while(downui.flag) {
                    Thread.sleep(200);
                };
                
                raf.write(buffer, 0, len);
                b.setValue(b.getValue()+len);
                synchronized (downui) {
                    downui.bar.setValue(downui.bar.getValue()+len);
                    downui.p.setProperty("Thread."+download.getIndex()+".start", Integer.parseInt(downui.p.getProperty("Thread."+download.getIndex()+".start"))+len + "");
                    downui.p.setProperty("Thread."+download.getIndex()+".size", Integer.parseInt(downui.p.getProperty("Thread."+download.getIndex()+".size"))-len + "");
                    downui.p.store(new FileOutputStream(downui.ProfilePath), "");
                }
            }
            raf.close();
            in.close();
             * 检测文件是否下载结束了,结束后删除线程数据文件(不过是删除失败的)
            if(isEnd()) {
                System.out.println(Thread.currentThread().getName() + " 文件下载结束了");
                System.out.println("删除数据文件");
                downui.p = null;
                File f = new File(downui.ProfilePath);
                if(f.delete()) {
                    System.out.println("删除成功");
                }else {
                    System.out.println("删除失败");
                }
            }
        } catch(Exception e) {
            e.printStackTrace();
            System.out.println(Thread.currentThread().getName() + "线程下载出错");
        }
    }

     * 检测所有线程是否下载结束了,注意:Properties是线程安全的,其内部使用了synchronized
    private boolean isEnd() {
        int count = Integer.parseInt(downui.p.getProperty("Thread.count"));
        for(int i=0; i 0) {
                return false;
            }
        }
        return true;
    }
}

 * 储存单个线程的下载信息
class Download{
    private String url;
    private String savePath;
    private int start;
    private int size;
    private int index;
    
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getSavePath() {
        return savePath;
    }
    public void setSavePath(String savePath) {
        this.savePath = savePath;
    }
    public int getStart() {
        return start;
    }
    public void setStart(int start) {
        this.start = start;
    }
    public int getSize() {
        return size;
    }
    public void setSize(int size) {
        this.size = size;
    }
    public int getIndex() {
        return index;
    }
    public void setIndex(int index) {
        this.index = index;
    }
    @Override
    public String toString() {
        return "Download [url=" + url + ", savePath=" + savePath + ", start=" + start + ", size=" + size + ", index="
                + index + "]";
    }
}

暂停下载


1.Java 的屏幕广播(基于UDP),2.多线程下载器_第3张图片

关闭程序之后再店家开始下载,可以实现断点续传


1.Java 的屏幕广播(基于UDP),2.多线程下载器_第4张图片

下载完成
1.Java 的屏幕广播(基于UDP),2.多线程下载器_第5张图片

你可能感兴趣的:(1.Java 的屏幕广播(基于UDP),2.多线程下载器)