FtpTransFile类
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
/**
* 在这个版本中希望做的是软件的多线程下载
* 现在也做了断点下载 ,是否断点下载的依据为目标文件是否已经存在 ,不管是否从断点出继续下载,
* 分的线程下载的文件都是从指定的位置开始下载的
*
*/
public class FtpTransFile {
private static String fileName; // 要上传或下载的文件的名字
private static String path;// 临时文件夹的目录,用于存放多个线程下载的文件
static long threadBlock = 100 * 1024 * 1024L;
/**
*
* @param path
* 要上传的本地文件路径 如"C:/Users/repace/Desktop/zhangke1.txt";
* @param server
* ftp服务器ip地址 192.168.242.133
* @param userName
* 登录ftp的用户名 test
* @param password
* 登录ftp用户名对应的密码 123456
*/
public static void fileUpload(String OStype, String path, String server,
String userName, String password) { // 要上传的文件的本地路径路径
// 目前可完成单个文件的上传
if (!(OStype.equalsIgnoreCase("windows") || OStype
.equalsIgnoreCase("linux"))) {
System.out.println("操作系统类型输入错误,应为windows或linux");
return;
}
FTPClient ftpClient = new FTPClient();
ftpClient.enterLocalPassiveMode(); // 这一句话一定要记得加上
FileInputStream fis = null;
try {
ftpClient.connect(server);
ftpClient.login(userName, password);
File srcFile = new File(path);// 要上传的本地文件路径
fis = new FileInputStream(srcFile);
String storeName = srcFile.getName();// 要存储的文件的名字
String remoteFilename = "/mnt/data/ftp/www/" + OStype.toLowerCase() + "/"
+ storeName;
ftpClient.changeWorkingDirectory("/mnt/data/ftp/www/" + OStype.toLowerCase()
+ "/"); // 设置上传的文件在centos上的目录,文件上传不成功是要查看指定目录的权限
ftpClient.setBufferSize(1024);
ftpClient.setControlEncoding("UTF-8");
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);// 设置文件类型(二进制)
FTPFile[] files = ftpClient.listFiles(remoteFilename);// 判断软件中心是否包含这个文件
if (files.length == 1) {// 软件中心包含该文件
long remoteSize = files[0].getSize();// 软件中心的文件大小
long localSize = srcFile.length();// 打算要上传的文件大小
if (remoteSize == localSize) { // 软件中心有这个文件,并且和打算要上传的文件大小一样,则说要上传的文件已存在
System.out.println("要上传的文件已存在");
ftpClient.disconnect();
return;
} else if (remoteSize > localSize) {// 软件中心的文件比要上传的大,可能新上传的文件被修改了,然后再次上传的
System.out.println("软件中心的软件比即将上传的要大,无须上传或重新命名要上传的文件名");
ftpClient.disconnect();
return;
}
// 软件中心存的文件比要上传的文件小,则尝试移动文件内读取指针,实现断点续传 **************
if (fis.skip(remoteSize) == remoteSize) {
ftpClient.setRestartOffset(remoteSize);
boolean i = ftpClient.storeFile(
new String(storeName.getBytes("UTF-8"),
"iso-8859-1"), fis);
if (i) {
System.out.println("文件断点续传成功");
ftpClient.disconnect();
return;
}
}
} else { // 软件中心不包含要上传的文件,或者续传不成功,则上传全新的文件即可
boolean i = ftpClient.storeFile(
new String(storeName.getBytes("UTF-8"), "iso-8859-1"),
fis);
System.out.println("文件上传" + i);
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("FTP客户端出错!", e);
} finally {
try {
fis.close();
ftpClient.disconnect();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException("关闭FTP连接发生异常!", e);
}
}
}
/**
* *
*
* @param OStype
* 操作系统的类型 windows或者是linux
* @param fileName
* 指出要下载的文件名字 加后缀的
* @param storePath
* 下载之后想要在本地的存储路径,在window系统中支持两种文件路径\\ 或者/
* @param server
* ftp服务器IP地址
* @param userName
* ftp分配的登录名 test
* @param password
* 与登录名对应的登录密码 123456
* @throws FileNotFoundException
* @throws InterruptedException
*/
public static void fileDownload(String OStype, String fileNames,
String storePath, String server, String userName, String password)
throws FileNotFoundException, InterruptedException { // 参数是带后缀的文件名字和下载之后要存储的本地路径
// 可完成单个文件的下载 ,
if (!(OStype.equalsIgnoreCase("windows") || OStype
.equalsIgnoreCase("linux"))) {
System.out.println("操作系统类型输入错误,应为windows或linux");
return;
}
fileName = fileNames;
File file = new File(storePath);
if (!file.exists()) {// 判断文件夹是否存在,如果不存在则创建文件夹
file.mkdir();
}
FTPClient ftpClient = new FTPClient();
ftpClient.enterLocalPassiveMode(); // 这一句话一定要记得加上
String remoteFileName = "/mnt/data/ftp/www/" + OStype.toLowerCase() + "/"
+ fileName; // 服务器上的文件,前面是文件夹的名字,后面的是文件的名字
String localFileName = "";// 本地要存储的文件绝对路径 文件夹加上文件名
try {
ftpClient.connect(server);
ftpClient.login(userName, password);
ftpClient.setBufferSize(1024);
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); // 设置文件类型(二进制)
FTPFile[] files = ftpClient.listFiles(remoteFileName);
if (files.length == 0) { // 判断软件中心是否有要下载的软件
System.out.println("软件中心没有找到要下载的软件");
ftpClient.disconnect();
return;
} else { //软件中心包含请求下载的文件
long localSize = 0L; // 记录本地文件的大小
if (storePath.endsWith("\\") || storePath.endsWith("/"))// 存储路径直接是某个盘下的根目录或者用户加上了最后的斜线
{
localFileName = storePath + fileName;
path = storePath
+ fileName.substring(0, fileName.indexOf("."))
+ "Temp/";
} else {
localFileName = storePath + "/" + fileName;
path = storePath + "/"
+ fileName.substring(0, fileName.indexOf("."))
+ "Temp/";
}
File localFile = new File(localFileName);
long remoteSize = files[0].getSize();// 软件中心的文件大小
if (localFile.exists()) {// 指定下载的文件在本地文件夹内已经存在
localSize = localFile.length();// 已存在的文件大小
if (remoteSize == localSize) {
System.out.println("文件已下载过,无需再下载");
return;
} else if (remoteSize > localSize) { // 之前下载未完成,实现断点下载
System.out.println("断点下载。。。");
}
if (remoteSize < localSize) {// 如果本地的文件比软件中心的文件大,则说明本地的文件可能有错,删除,然后从头开始下载
localFile.delete();
System.out.println("软件从新开始下载");
localSize = 0L;
}
} else {// 指定下载的文件在本地文件夹内不存在,从头下载文件
localSize = 0L;
System.out.println("软件从头下载");
}
File tempfile = new File(path);
if (tempfile.exists()) {// 判断文件夹是否存在,如果已经存在,则删除该文件夹及其所有的子文件,以免其包含的线程影响后面的下载过程
System.out.println("delete 之前的临时文件夹");
deleteTempFile(path);
}
tempfile.mkdir();// 新建存放临时文件夹的目录
ExecutorService exec = Executors.newCachedThreadPool(); // 开始启动多线程下载文件
int threadNum = (int) ((remoteSize - localSize) / threadBlock + 1);// 每100M分一个线程下载
// 计算线程总数
System.out.println("分成的线程个数" + threadNum);
CountDownLatch latch = new CountDownLatch(threadNum);
System.out.println(fileNames + "请求还需下载的文件大小"
+ (remoteSize - localSize));
long[] startPos = new long[threadNum];
ChildThread[] childThreads = new ChildThread[threadNum];// ChildThread
// 变成ChildThread1共有4处修改
for (int i = 0; i < threadNum; i++) {
startPos[i] = localSize + i * threadBlock; // 设置每个线程开始下载文件的起始位置
childThreads[i] = new ChildThread(OStype, fileName,
storePath, server, userName, password, startPos[i],
i, latch); // 创建线程 线程编号从0开始
exec.execute(childThreads[i]);// 开始执行线程
}
latch.await(); // 等待所有的线程都运行结束
exec.shutdown();
tempFileToTargetFile(localFileName, childThreads, threadNum);// 把临时得到的文件夹内的文件合并到目标文件
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("FTP客户端出错!", e);
} finally {
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("关闭FTP连接发生异常!", e);
}
}
}
/**
* @author repace 把临时文件夹内的文件都写入目标文件内 即将各个线程所下的文件进行合并
* @param target
* 目标文件 是之前用户发送的请求要把请求下载的文件存放的绝对路径的目录
* 比如要下载的是test.txt文件,想存在c:\\123\\文件夹内 则目标文件target
* 指的的就是c:\\123\\test.txt
* @param tempFile
* 临时文件夹的目录则是 c:\\123\\testTemp\\
* @param threadNum
* @return
* @throws IOException
*/
public static boolean tempFileToTargetFile(String target,
ChildThread[] childThreads, int threadNum) throws IOException { // 完成把临时文件夹内的日志都写到目标文件中
System.out.println("KAISHI HEBING");
boolean result = true;
FileInputStream inputStream = null;
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(target, true); // 追加内容
for (int i = 0; i < threadNum; i++) { // 遍历所有子线程创建的临时文件,按顺序把下载内容写入目标文件中
inputStream = new FileInputStream(
childThreads[i].localTempFileName);
int len = 0;
byte[] b = new byte[1024];
int count = 0;
while ((len = inputStream.read(b)) != -1) {
outputStream.write(b, 0, len);
outputStream.flush();
count += len;
}
inputStream.close();
}
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (outputStream != null) {
outputStream.close();
}
File file = new File(target);
System.out.print(target + "下载得到的文件大小是 " + file.length());
deleteTempFile(path);// 删除临时文件夹
return result;
}
public static void deleteTempFile(String Path) {//删除临时文件夹
File file = new File(Path);
if (file.isFile()) {// 表示该文件不是文件夹
file.delete();
} else {
// 首先得到当前的路径
String[] childFilePaths = file.list();
for (String childFilePath : childFilePaths) {
File childFile = new File(file.getAbsolutePath() + "/"
+ childFilePath);
String s = childFile.getAbsolutePath();
deleteTempFile(s);
}
file.delete();
}
}
}
|
ChildThread类
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
import org.apache.commons.net.ftp.FTPClient;
public class ChildThread extends Thread {
public int id;
private long startPosition;
CountDownLatch latch;
String remoteFileName; //要下载的文件在软件中心的文件
String localTempFileName; //用于存放每个线程下载的临时文件的绝对路径 (带上临时文件的名字和后缀)
String path;//临时文件夹的目录
FTPClient ftpClient = new FTPClient();
public ChildThread(String OStype,String fileName, String storePath,
String server, String userName, String password,long startPos,int id,CountDownLatch latch) {
ftpClient.enterLocalPassiveMode(); // 这一句话一定要记得加上
remoteFileName = "/mnt/data/ftp/www/"+OStype.toLowerCase() +"/"+ fileName; // 服务器上的文件,前面是文件夹的名字,后面的是文件的名字
startPosition=startPos;
this.latch=latch;
this.id=id;
try {
ftpClient.connect(server);
ftpClient.login(userName, password);
ftpClient.setBufferSize(1024);
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); // 设置文件类型(二进制)
if (storePath.endsWith("\\") || storePath.endsWith("/"))//给出的路径下新建一个临时文件夹,里面存储的是各个线程下载的文件
{
localTempFileName=storePath +fileName.substring(0, fileName.indexOf("."))+"Temp/" +id+"_"+fileName;//保证临时文件夹唯一 也应保证临时文件的命名唯一
} else{
localTempFileName=storePath + "/" +fileName.substring(0, fileName.indexOf("."))+"Temp/" + id+"_"+fileName;
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("FTP客户端出错!", e);
} finally {
}
}
public void run() {
FileOutputStream outputStream = null;
try {
File threadTempFile=new File(localTempFileName);
outputStream = new FileOutputStream(localTempFileName,true);
ftpClient.setRestartOffset(startPosition+threadTempFile.length()); //设置每个线程开始的下载位置 如果之前threadTempFile.length()不等于0,则从上次那个地方继续下载 断点下载
InputStream in= ftpClient.retrieveFileStream(remoteFileName);
int len = 0;
byte[] b = new byte[1024];
long count=threadTempFile.length();
while((len = in.read(b)) != -1) {
count +=len;//记录文件中的长度加上这次准备写的长度
if (count > FtpTransFile.threadBlock) { //加上最后一次读到的已经比规定的线程块大,则只取前面一部分即可
int lastLen= (int) (FtpTransFile.threadBlock-threadTempFile.length());
outputStream.write(b, 0,lastLen);//方法write(b, off, len),b[off]是写入的第一个字节和b[off+len-1]是写的这个操作的最后一个字节。
outputStream.flush();
break;
}
outputStream.write(b, 0, len);
outputStream.flush();
}
in.close();//关闭流
File file=new File(localTempFileName);
System.out.println("Thread file "+id+" "+file.length());
outputStream.close();
ftpClient.disconnect();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
latch.countDown();//每个线程结束的时候,则总的线程数减1
}
}
|