1 package com.hirain.ftp.thread; 2 3 import java.io.File; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 import java.io.RandomAccessFile; 9 10 import org.apache.commons.net.ftp.FTP; 11 import org.apache.commons.net.ftp.FTPClient; 12 import org.apache.commons.net.ftp.FTPFile; 13 import org.apache.commons.net.ftp.FTPReply; 14 15 public class FTPDownloadThread extends Thread { 16 17 // 枚举类UploadStatus代码 18 19 public enum UploadStatus { 20 Create_Directory_Fail, // 远程服务器相应目录创建失败 21 Create_Directory_Success, // 远程服务器闯将目录成功 22 Upload_New_File_Success, // 上传新文件成功 23 Upload_New_File_Failed, // 上传新文件失败 24 File_Exits, // 文件已经存在 25 Remote_Bigger_Local, // 远程文件大于本地文件 26 Upload_From_Break_Success, // 断点续传成功 27 Upload_From_Break_Failed, // 断点续传失败 28 Delete_Remote_Faild; // 删除远程文件失败 29 } 30 31 // 枚举类DownloadStatus代码 32 public enum DownloadStatus { 33 Remote_File_Noexist, // 远程文件不存在 34 Local_Bigger_Remote, // 本地文件大于远程文件 35 Download_From_Break_Success, // 断点下载文件成功 36 Download_From_Break_Failed, // 断点下载文件失败 37 Download_New_Success, // 全新下载文件成功 38 Download_New_Failed; // 全新下载文件失败 39 } 40 41 public FTPClient ftpClient = new FTPClient(); 42 private String ftpURL, username, pwd, ftpport, file1, file2; 43 44 public FTPDownloadThread(String _ftpURL, String _username, String _pwd, 45 String _ftpport, String _file1, String _file2) { 46 // 设置将过程中使用到的命令输出到控制台 47 ftpURL = _ftpURL; 48 username = _username; 49 pwd = _pwd; 50 ftpport = _ftpport; 51 file1 = _file1; 52 file2 = _file2; 53 } 54 55 /** 56 * 连接到FTP服务器 57 * 58 * @param hostname 59 * 主机名 60 * @param port 61 * 端口 62 * @param username 63 * 用户名 64 * @param password 65 * 密码 66 * @return 是否连接成功 67 * @throws IOException 68 */ 69 public boolean connect(String hostname, int port, String username, 70 String password) throws IOException { 71 ftpClient.connect(hostname, port); 72 ftpClient.setControlEncoding("GBK"); 73 if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) { 74 if (ftpClient.login(username, password)) { 75 return true; 76 } 77 } 78 disconnect(); 79 return false; 80 } 81 82 /** 83 * 从FTP服务器上下载文件,支持断点续传,上传百分比汇报 84 * 85 * @param remote 86 * 远程文件路径 87 * @param local 88 * 本地文件路径 89 * @return 上传的状态 90 * @throws IOException 91 */ 92 public DownloadStatus download(String remote, String local) 93 throws IOException { 94 // 设置被动模式 95 ftpClient.enterLocalPassiveMode(); 96 // 设置以二进制方式传输 97 ftpClient.setFileType(FTP.BINARY_FILE_TYPE); 98 DownloadStatus result; 99 100 // 检查远程文件是否存在 101 FTPFile[] files = ftpClient.listFiles(new String( 102 remote.getBytes("GBK"), "iso-8859-1")); 103 if (files.length != 1) { 104 System.out.println("远程文件不存在"); 105 return DownloadStatus.Remote_File_Noexist; 106 } 107 108 long remoteSize = files[0].getSize(); 109 File f = new File(local); 110 // 本地存在文件,进行断点下载 111 if (f.exists()) { 112 long localSize = f.length(); 113 // 判断本地文件大小是否大于远程文件大小 114 if (localSize >= remoteSize) { 115 System.out.println("本地文件大于远程文件,下载中止"); 116 return DownloadStatus.Local_Bigger_Remote; 117 } 118 119 // 进行断点续传,并记录状态 120 FileOutputStream out = new FileOutputStream(f, true); 121 ftpClient.setRestartOffset(localSize); 122 InputStream in = ftpClient.retrieveFileStream(new String(remote 123 .getBytes("GBK"), "iso-8859-1")); 124 byte[] bytes = new byte[1024]; 125 long step = remoteSize / 100; 126 long process = localSize / step; 127 int c; 128 while ((c = in.read(bytes)) != -1) { 129 out.write(bytes, 0, c); 130 localSize += c; 131 long nowProcess = localSize / step; 132 if (nowProcess > process) { 133 process = nowProcess; 134 if (process % 10 == 0) 135 System.out.println("下载进度:" + process); 136 // TODO 更新文件下载进度,值存放在process变量中 137 } 138 } 139 in.close(); 140 out.close(); 141 boolean isDo = ftpClient.completePendingCommand(); 142 if (isDo) { 143 result = DownloadStatus.Download_From_Break_Success; 144 } else { 145 result = DownloadStatus.Download_From_Break_Failed; 146 } 147 } else { 148 OutputStream out = new FileOutputStream(f); 149 InputStream in = ftpClient.retrieveFileStream(new String(remote 150 .getBytes("GBK"), "iso-8859-1")); 151 byte[] bytes = new byte[1024]; 152 long step = remoteSize / 100; 153 long process = 0; 154 long localSize = 0L; 155 int c; 156 while ((c = in.read(bytes)) != -1) { 157 out.write(bytes, 0, c); 158 localSize += c; 159 long nowProcess = localSize / step; 160 if (nowProcess > process) { 161 process = nowProcess; 162 if (process % 10 == 0) 163 System.out.println("下载进度:" + process); 164 // TODO 更新文件下载进度,值存放在process变量中 165 } 166 } 167 in.close(); 168 out.close(); 169 boolean upNewStatus = ftpClient.completePendingCommand(); 170 if (upNewStatus) { 171 result = DownloadStatus.Download_New_Success; 172 } else { 173 result = DownloadStatus.Download_New_Failed; 174 } 175 } 176 return result; 177 } 178 179 /** 180 * 上传文件到FTP服务器,支持断点续传 181 * 182 * @param local 183 * 本地文件名称,绝对路径 184 * @param remote 185 * 远程文件路径,使用/home/directory1/subdirectory/file.ext或是 186 * http://www.guihua.org /subdirectory/file.ext 187 * 按照Linux上的路径指定方式,支持多级目录嵌套,支持递归创建不存在的目录结构 188 * @return 上传结果 189 * @throws IOException 190 */ 191 public UploadStatus upload(String local, String remote) throws IOException { 192 // 设置PassiveMode传输 193 ftpClient.enterLocalPassiveMode(); 194 // 设置以二进制流的方式传输 195 ftpClient.setFileType(FTP.BINARY_FILE_TYPE); 196 ftpClient.setControlEncoding("GBK"); 197 UploadStatus result; 198 // 对远程目录的处理 199 String remoteFileName = remote; 200 if (remote.contains("/")) { 201 remoteFileName = remote.substring(remote.lastIndexOf("/") + 1); 202 // 创建服务器远程目录结构,创建失败直接返回 203 if (CreateDirecroty(remote, ftpClient) == UploadStatus.Create_Directory_Fail) { 204 return UploadStatus.Create_Directory_Fail; 205 } 206 } 207 208 // 检查远程是否存在文件 209 FTPFile[] files = ftpClient.listFiles(new String(remoteFileName 210 .getBytes("GBK"), "iso-8859-1")); 211 if (files.length == 1) { 212 long remoteSize = files[0].getSize(); 213 File f = new File(local); 214 long localSize = f.length(); 215 if (remoteSize == localSize) { 216 return UploadStatus.File_Exits; 217 } else if (remoteSize > localSize) { 218 return UploadStatus.Remote_Bigger_Local; 219 } 220 221 // 尝试移动文件内读取指针,实现断点续传 222 result = uploadFile(remoteFileName, f, ftpClient, remoteSize); 223 224 // 如果断点续传没有成功,则删除服务器上文件,重新上传 225 if (result == UploadStatus.Upload_From_Break_Failed) { 226 if (!ftpClient.deleteFile(remoteFileName)) { 227 return UploadStatus.Delete_Remote_Faild; 228 } 229 result = uploadFile(remoteFileName, f, ftpClient, 0); 230 } 231 } else { 232 result = uploadFile(remoteFileName, new File(local), ftpClient, 0); 233 } 234 return result; 235 } 236 237 /** 238 * 断开与远程服务器的连接 239 * 240 * @throws IOException 241 */ 242 public void disconnect() throws IOException { 243 if (ftpClient.isConnected()) { 244 ftpClient.disconnect(); 245 } 246 } 247 248 /** 249 * 递归创建远程服务器目录 250 * 251 * @param remote 252 * 远程服务器文件绝对路径 253 * @param ftpClient 254 * FTPClient 对象 255 * @return 目录创建是否成功 256 * @throws IOException 257 */ 258 public UploadStatus CreateDirecroty(String remote, FTPClient ftpClient) 259 throws IOException { 260 UploadStatus status = UploadStatus.Create_Directory_Success; 261 String directory = remote.substring(0, remote.lastIndexOf("/") + 1); 262 if (!directory.equalsIgnoreCase("/") 263 && !ftpClient.changeWorkingDirectory(new String(directory 264 .getBytes("GBK"), "iso-8859-1"))) { 265 // 如果远程目录不存在,则递归创建远程服务器目录 266 int start = 0; 267 int end = 0; 268 if (directory.startsWith("/")) { 269 start = 1; 270 } else { 271 start = 0; 272 } 273 end = directory.indexOf("/", start); 274 while (true) { 275 String subDirectory = new String(remote.substring(start, end) 276 .getBytes("GBK"), "iso-8859-1"); 277 if (!ftpClient.changeWorkingDirectory(subDirectory)) { 278 if (ftpClient.makeDirectory(subDirectory)) { 279 ftpClient.changeWorkingDirectory(subDirectory); 280 } else { 281 System.out.println("创建目录失败"); 282 return UploadStatus.Create_Directory_Fail; 283 } 284 } 285 start = end + 1; 286 end = directory.indexOf("/", start); 287 // 检查所有目录是否创建完毕 288 if (end <= start) { 289 break; 290 } 291 } 292 } 293 return status; 294 } 295 296 /** */ 297 /** 298 * 上传文件到服务器,新上传和断点续传 299 * 300 * @param remoteFile 301 * 远程文件名,在上传之前已经将服务器工作目录做了改变 302 * @param localFile 303 * 本地文件 File句柄,绝对路径 304 * @param processStep 305 * 需要显示的处理进度步进值 306 * @param ftpClient 307 * FTPClient 引用 308 * @return 309 * @throws IOException 310 */ 311 public UploadStatus uploadFile(String remoteFile, File localFile, 312 FTPClient ftpClient, long remoteSize) throws IOException { 313 UploadStatus status; 314 // 显示进度的上传 315 long step = localFile.length() / 100; 316 long process = 0; 317 long localreadbytes = 0L; 318 RandomAccessFile raf = new RandomAccessFile(localFile, "r"); 319 OutputStream out = ftpClient.appendFileStream(new String(remoteFile 320 .getBytes("GBK"), "iso-8859-1")); 321 // 断点续传 322 if (remoteSize > 0) { 323 ftpClient.setRestartOffset(remoteSize); 324 process = remoteSize / step; 325 raf.seek(remoteSize); 326 localreadbytes = remoteSize; 327 } 328 byte[] bytes = new byte[1024]; 329 int c; 330 while ((c = raf.read(bytes)) != -1) { 331 out.write(bytes, 0, c); 332 localreadbytes += c; 333 if (localreadbytes / step != process) { 334 process = localreadbytes / step; 335 System.out.println("上传进度:" + process); 336 } 337 } 338 out.flush(); 339 raf.close(); 340 out.close(); 341 boolean result = ftpClient.completePendingCommand(); 342 if (remoteSize > 0) { 343 status = result ? UploadStatus.Upload_From_Break_Success 344 : UploadStatus.Upload_From_Break_Failed; 345 } else { 346 status = result ? UploadStatus.Upload_New_File_Success 347 : UploadStatus.Upload_New_File_Failed; 348 } 349 return status; 350 } 351 352 public void run() { 353 try { 354 this.connect(ftpURL, new java.lang.Integer(ftpport), username, pwd); 355 this.download(file1, file2); 356 this.disconnect(); 357 } catch (IOException e) { 358 System.out.println("连接FTP出错:" + e.getMessage()); 359 } 360 } 361 362 }