java socket udp 广播_1.Java 的屏幕广播(基于UDP),2.多线程下载器
发布日期:2021-06-24 11:48:42 浏览次数:4 分类:技术文章

本文共 21046 字,大约阅读时间需要 70 分钟。

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)<

* 其中的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

* 如果是最后一个包,就将剩下的所有数据都发送出去

if(i == count-1) {

buffer = new byte[len%(60*1024)+16];

}

* 写入时间戳

System.arraycopy(Utils.long2Byte(time), 0, buffer, 0, 8);

* 写入分割的数量

System.arraycopy(Utils.int2Byte(count), 0, buffer, 8, 4);

* 写入当前的编号,从0开始

System.arraycopy(Utils.int2Byte(i), 0, buffer, 12, 4);

* 写入image内容

System.arraycopy(src, i*60*1024, buffer, 16, buffer.length-16);

System.out.println("i = " + (i) + ", buffer.length = " + buffer.length);

* 发送数据,组装成数据报包

DatagramPacket pack = new DatagramPacket(buffer, buffer.length);

* 设置发送地址、端口

pack.setSocketAddress(new InetSocketAddress("localhost", 9999));

socket.send(pack);

System.out.println("发送一个数据报包");

}

} catch(Exception e) {

e.printStackTrace();

}

}

}

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

是用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

PackInfo p = entry.getValue().get(i);

baos.write(p.getData());

}

baos.close();

* 解压缩数据

byte[] b = unCompressImage(baos.toByteArray());

System.out.println("未解压缩的数据长度 = " + baos.toByteArray().length);

System.out.println("解压缩后的图片长度 = " + b.length);

* 设置图片到面板中

BufferedImage image = ImageIO.read(new ByteArrayInputStream(b));

ImageIcon i = new ImageIcon(image);

label.setIcon(i);

repaint();

System.out.println("设置一张图片");

* 将设置过后的image数据从map中移除

it.remove();

}

}

System.out.println("字典长度 = " + map.size());

}

* 对合成的数据进行解压缩,返回解压的byte[]

private byte[] unCompressImage(byte[] b) throws IOException {

ByteArrayInputStream bais = new ByteArrayInputStream(b);

ZipInputStream zis = new ZipInputStream(bais);

zis.getNextEntry();

byte[] data = zis.readAllBytes();

zis.close();

bais.close();

return data;

}

}

* 存放包的数据的实例类

class PackInfo {

private long time; * 包的时间戳

private int count; * 分割的包数量

private int num; * 当前的包编号

private byte[] data; * 包中存放的数据

public long getTime() {

return time;

}

public void setTime(long time) {

this.time = time;

}

public int getCount() {

return count;

}

public void setCount(int count) {

this.count = count;

}

public int getNum() {

return num;

}

public void setNum(int num) {

this.num = num;

}

public byte[] getData() {

return data;

}

public void setData(byte[] data) {

this.data = data;

}

}

程序的效果如下图

接受者启动的初始画面,后台线程正在等待接收数据

05b3dc0f42bc

启动广播者,不断的在发送数据包

05b3dc0f42bc

可以看到面板中的实时屏幕广播了

05b3dc0f42bc

多线程下载器

使用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

Download download = new Download();

if(i != count-1) { * 不是最后一个线程,下载量就是block

download.setSize(block);

}else { * 最后一个线程,下载量是总量减去之前线程的下载量

download.setSize(fileSize-(count-1)*block);

}

download.setUrl(url);

download.setSavePath(savePath);

download.setIndex(i); * 设置线程编号

download.setStart(i*block); * 设置线程的开始下载位置

lists.add(download);

}

return lists;

} catch(Exception e) {

e.printStackTrace();

}

return null;

}

* 断点续传的,取出上次下载的线程储存的数据文件,读取到Download中,构成list返回

public List 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

Download d = new Download();

d.setUrl(url);

d.setSavePath(savePath);

d.setStart(Integer.parseInt(downui.p.getProperty("Thread." + i + ".start")));

d.setIndex(i);

d.setSize(Integer.parseInt(downui.p.getProperty("Thread." + i + ".size")));

lists.add(d);

}

return lists;

}

* 启动所有线程开始下载

public void startDownload(List 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

int size = Integer.parseInt(downui.p.getProperty("Thread." + i + ".size"));

if(size > 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 + "]";

}

}

暂停下载

05b3dc0f42bc

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

05b3dc0f42bc

下载完成

05b3dc0f42bc

转载地址:https://blog.csdn.net/weixin_32573931/article/details/114206738 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:java控制热敏打印机的例子.rar_stm32控制热敏打印机
下一篇:java上传下载源码_javaweb简单实现文件上传与下载源代码

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年03月30日 03时08分39秒