由于要用APPLET实现大文件FTP上传下载,从网上搜索了几下,找到很多资料,最后决定采用基于 org.apache.commons.net.ftp包实现FTP上传下载,Net包中的类既提供对协议的底层访问也有高层的抽象。在大多数情况下,抽 象是足够的,它可以使你不必编写解析各种协议的底层套接字的代码。使用抽象不会损失任何功能。
借此感叹,org.apache.commons开源包真是森罗万象,应有尽有。反观国内打着开源旗号的软件也不少,但没几个能在当前软林扬名立万的,借山本赵一句经典台词:悲哀 悲哀 真是悲哀。
开发环境:winxp+eclipse3.2.2+struts2.0+jse5.0
最早采用以下代码:
package yp;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
public class ContinueFTP{
private File file=null;
//是否完整保留原文件名
private boolean isSaveFileName = true;
//枚举上传状态
public enum UploadStatus {
Create_Directory_Fail, //远程服务器相应目录创建失败
Create_Directory_Success, //远程服务器创建目录成功
Upload_New_File_Success, //上传新文件成功
Upload_New_File_Failed, //上传新文件失败
File_Exits, //文件已经存在
Remote_Bigger_Local, //远程文件大于本地文件
Upload_From_Break_Success, //断点续传成功
Upload_From_Break_Failed, //断点续传失败
Delete_Remote_Faild; //删除远程文件失败
}
//枚举下载状态
public enum DownloadStatus {
Remote_File_Noexist, //远程文件不存在
Local_Bigger_Remote, //本地文件大于远程文件
Download_From_Break_Success, //断点下载文件成功
Download_From_Break_Failed, //断点下载文件失败
Download_New_Success, //全新下载文件成功
Download_New_Failed; //全新下载文件失败
}
public void init(){
}
public FTPClient ftpClient = new FTPClient();
public ContinueFTP(){
//设置将过程中使用到的命令输出到控制台
this.ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
}
public String getRemoteFileName(String localFileName){
String fileName ="";
//分隔符
String sepaRator="\";
if (localFileName.indexOf(sepaRator)<0)
sepaRator="/";
//最后分隔符位置
int idx = localFileName.lastIndexOf(sepaRator)+1;
fileName = localFileName.substring(idx);
return fileName;
}
public boolean isFileExist(String remoteFileName) throws IOException{
boolean isFileExist = false;
//检查远程是否存在文件
FTPFile[] files = ftpClient.listFiles(new String(remoteFileName.getBytes("GBK"),"iso-8859-1"));
if(files!=null && files.length >= 1){
isFileExist = true;
}
return isFileExist;
}
public boolean connect(String hostname,int port,String username,String password) throws Exception{
boolean bl = false;
try{
ftpClient.connect(hostname, port);
}catch(Exception e){
//可具体报错到主机和端口号
throw new BaseException("FTPConnError01",new String[]{"connect",e.getMessage()});
}
try{
//ftpClient.setControlEncoding("GBK");
if(FTPReply.isPositiveCompletion(ftpClient.getReplyCode())){
if(ftpClient.login(username, password)){
bl = true;
}
}
}catch(Exception e){
//可具体报错到用户和密码
throw new BaseException("FTPConnError02",new String[]{"connect",e.getMessage()});
}
return bl;
}
public DownloadStatus download(String remote,String local) throws Exception{
//设置被动模式
ftpClient.enterLocalPassiveMode();
//设置以二进制方式传输
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
DownloadStatus result;
//检查远程文件是否存在
FTPFile[] files = ftpClient.listFiles(new String(remote.getBytes("GBK"),"iso-8859-1"));
if(files.length != 1){
throw new BaseException("CellDataInputService",new String[]{"download","远程文件"+remote+"不存在"});
}
long lRemoteSize = files[0].getSize();
File f = new File(local);
//本地存在文件,进行断点下载
if(f.exists()){
long localSize = f.length();
//判断本地文件大小是否大于远程文件大小
if(localSize >= lRemoteSize){
System.out.println("本地文件大于远程文件,下载中止");
return DownloadStatus.Local_Bigger_Remote;
}
//进行断点续传,并记录状态
FileOutputStream out = new FileOutputStream(f,true);
ftpClient.setRestartOffset(localSize);
InputStream in = ftpClient.retrieveFileStream(new String(remote.getBytes("GBK"),"iso-8859-1"));
byte[] bytes = new byte[1024];
long step = lRemoteSize /100;
long process=localSize /step;
int c;
while((c = in.read(bytes))!= -1){
out.write(bytes,0,c);
localSize+=c;
long nowProcess = localSize /step;
if(nowProcess > process){
process = nowProcess;
if(process % 10 == 0)
System.out.println("下载进度:"+process);
//TODO 更新文件下载进度,值存放在process变量中
}
}
in.close();
out.close();
boolean isDo = ftpClient.completePendingCommand();
if(isDo){
result = DownloadStatus.Download_From_Break_Success;
}else {
result = DownloadStatus.Download_From_Break_Failed;
}
}else {
OutputStream out = new FileOutputStream(f);
InputStream in= ftpClient.retrieveFileStream(new String(remote.getBytes("GBK"),"iso-8859-1"));
byte[] bytes = new byte[1024];
long step = lRemoteSize /100;
long process=0;
long localSize = 0L;
int c;
while((c = in.read(bytes))!= -1){
out.write(bytes, 0, c);
localSize+=c;
long nowProcess = localSize /step;
if(nowProcess > process){
process = nowProcess;
if(process % 10 == 0)
System.out.println("下载进度:"+process);
//TODO 更新文件下载进度,值存放在process变量中
}
}
in.close();
out.close();
boolean upNewStatus = ftpClient.completePendingCommand();
if(upNewStatus){
result = DownloadStatus.Download_New_Success;
}else {
result = DownloadStatus.Download_New_Failed;
}
}
return result;
}
public UploadStatus upload(File localFile,String remote) throws IOException{
//设置PassiveMode传输
ftpClient.enterLocalPassiveMode();
//设置以二进制流的方式传输
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
//ftpClient.setControlEncoding("GBK");
UploadStatus result;
//对远程目录的处理
String remoteFileName = remote;
if(remote.contains("/")){
remoteFileName = remote.substring(remote.lastIndexOf("/")+1);
//创建服务器远程目录结构,创建失败直接返回
if(CreateDirecroty(remote, ftpClient)==UploadStatus.Create_Directory_Fail){
return UploadStatus.Create_Directory_Fail;
}
}
//检查远程是否存在文件
FTPFile[] files = ftpClient.listFiles(new String(remoteFileName.getBytes("GBK"),"iso-8859-1"));
if(files!=null && files.length == 1){
long remoteSize = files[0].getSize();
//File f = new File(local);
long localSize = localFile.length();
if(remoteSize==localSize){
return UploadStatus.File_Exits;
}else if(remoteSize > localSize){
return UploadStatus.Remote_Bigger_Local;
}
//尝试移动文件内读取指针,实现断点续传
result = uploadFile(remoteFileName, localFile, ftpClient, remoteSize);
//如果断点续传没有成功,则删除服务器上文件,重新上传
if(result == UploadStatus.Upload_From_Break_Failed){
if(!ftpClient.deleteFile(remoteFileName)){
return UploadStatus.Delete_Remote_Faild;
}
result = uploadFile(remoteFileName, localFile, ftpClient, 0);
}
}else {
result = uploadFile(remoteFileName, localFile, ftpClient, 0);
}
return result;
}
public void disconnect() throws IOException{
if(this.ftpClient.isConnected()){
this.ftpClient.disconnect();
}
}
public String CreateDirecroty(String remoteDir)throws IOException{
String fillDir = "";
UploadStatus st = CreateDirecroty(remoteDir,this.ftpClient);
if (st == UploadStatus.Create_Directory_Success)
fillDir = remoteDir;
else
fillDir = "";
return fillDir;
}
public UploadStatus CreateDirecroty(String remote,FTPClient ftpClient) throws IOException{
UploadStatus status = UploadStatus.Create_Directory_Success;
String directory = remote.substring(0,remote.lastIndexOf("/")+1);
if(!directory.equalsIgnoreCase("/")&&!ftpClient.changeWorkingDirectory(new String(directory.getBytes("GBK"),"iso-8859-1"))){
//如果远程目录不存在,则递归创建远程服务器目录
int start=0;
int end = 0;
if(directory.startsWith("/")){
start = 1;
}else{
start = 0;
}
end = directory.indexOf("/",start);
while(true){
String subDirectory = new String(remote.substring(start,end).getBytes("GBK"),"iso-8859-1");
if(!ftpClient.changeWorkingDirectory(subDirectory)){
if(ftpClient.makeDirectory(subDirectory)){
ftpClient.changeWorkingDirectory(subDirectory);
}else {
System.out.println("创建目录失败");
return UploadStatus.Create_Directory_Fail;
}
}
start = end + 1;
end = directory.indexOf("/",start);
//检查所有目录是否创建完毕
if(end <= start){
break;
}
}
}
return status;
}
public UploadStatus uploadFile(String remoteFile,File localFile,FTPClient ftpClient,long remoteSize) throws IOException{
UploadStatus status;
//显示进度的上传
long step = localFile.length() / 100;
long process = 0;
long localreadbytes = 0L;
RandomAccessFile raf = new RandomAccessFile(localFile,"r");
OutputStream out = ftpClient.appendFileStream(new String(remoteFile.getBytes("GBK"),"iso-8859-1"));
//断点续传
if(remoteSize>0){
ftpClient.setRestartOffset(remoteSize);
process = remoteSize /step;
raf.seek(remoteSize);
localreadbytes = remoteSize;
}
byte[] bytes = new byte[1024];
int c;
while((c = raf.read(bytes))!= -1){
out.write(bytes,0,c);
localreadbytes+=c;
//TODO 汇报上传状态
if(localreadbytes / step != process){
process = localreadbytes / step;
System.out.println("上传进度:" + process);
}
}
out.flush();
raf.close();
out.close();
boolean result =ftpClient.completePendingCommand();
if(remoteSize > 0){
status = result?UploadStatus.Upload_From_Break_Success:UploadStatus.Upload_From_Break_Failed;
}else {
status = result?UploadStatus.Upload_New_File_Success:UploadStatus.Upload_New_File_Failed;
}
return status;
}
public List<String> getRemoteFileList(String remoteDir){
List<String> list = new ArrayList<String>();
FTPFile[] files;
try {
files = ftpClient.listFiles(remoteDir);
for (int i = 0; i < files.length; i++) {
list.add(files[i].getName());
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return list;
}
public boolean deleteFile(String fillFileName) throws Exception {
boolean bl = false;
this.ftpClient.deleteFile(fillFileName);
int status = this.ftpClient.getReplyCode();
if(status == 250){
bl = true;
System.out.println("成功删除FTP服务器中文件:" + fillFileName);
}
return bl;
}
public boolean deleteDir(String remoteDir)throws Exception {
boolean isDel = false;
this.ftpClient.removeDirectory(remoteDir);
int status = this.ftpClient.getReplyCode();
if(status == 250){
isDel = true;
System.out.println("成功删除FTP服务器中目录:" + remoteDir);
}
return isDel;
}
public static void main(String[] args) throws Exception {
ContinueFTP myFtp = new ContinueFTP();
try {
long l1 = System.currentTimeMillis();
System.out.println("begin:"+ l1);
if (myFtp.connect("10.68.7.182", 21, "a", "a")) {
String mkDir = myFtp.CreateDirecroty("TTT/ccc/");
if (mkDir != null && !mkDir.trim().equals(""))
System.out.println("mkDir success:"+mkDir);
//myFtp.download( "/XA01B03H05/5.mp3",file,"0");
//myFtp.upload("/XA01B03H05/5.mp3", "/云台山.mpg");
//myFtp.delete_file("/tmp.txt");
//String str = new String("电视剧");
//myFtp.ftpClient.removeDirectory("/kkk/jk/");
//myFtp.ftpClient.makeDirectory(new String(str.getBytes("GBK"),"iso-8859-1"));
myFtp.disconnect();
long l2 = System.currentTimeMillis();
System.out.println("end:"+ l2);
System.out.println("remaining:"+(l2-l1));
}
} catch (IOException e) {
System.out.println("连接FTP出错:"+e.getMessage());
}
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public boolean isSaveFileName() {
return isSaveFileName;
}
public void setSaveFileName(boolean isSaveFileName) {
this.isSaveFileName = isSaveFileName;
}
}
通过main函数测试效果也不错,还显示上传或下载进度,再写一个对应action及jsp实现ftp上传下载,不过我吃过一次大亏,本来web服务器是本机,我原来做的上传下载都没有问题,但web服务器不在本机,就报说找不到文件!
后来终于想明白了,系统还是web服务器上路径找文件,当然找不到。
难道通过浏览器访问本地资源就真的无解了吗?
当然不是,也在找了很多解决方案,各有所长,又各有短处.
1.使用表单直接提交
这个方案肯定是被弊掉的,人一多就可能拖跨服务器.
2.使用FLASH进行文件上传
这个方案也尝试过,不过据说AS3只支持100M左右的文件上传,大文件无法上传,不知道是不是这样?如果是这样的话,那么这个方案也将行不通了.使用FLASH进行文件上传相对而言开发不会太困难.
3.使用APPLET进行文件上传
使用APPLET开发文件上传控件,这个对于使用JAVA来进行开发的非常方便,因为我们项目就是使用JAVA的,不过APPLET有安全策略的问题,无 法读取客户端的问题.如果要,那么用户必须修改其java.policy文件,这对于用户来讲简直是不可能的.如果使用程序下载动态修改也会比较麻烦.其 实还有一个破解之法,就是采用数字证书。
缺点:1)、客户端必须安装JRE;2)、想办法突破APPLET有安全策略的问题
优点:java编写,支持多浏览器
4.使用ACTIVEX进行文件上传
这是我目前认为比较可行的方式,而且网上也有很多类似这样的控件,但是都不是免费的,所以只能自己想办法解决了.使用ACTIVEX开发,可以使用C++、DELPHI等来进行开发
缺点:1>客户端推荐IE;2>IE安全设置中启用activex控件
优点:ACTIVEX开发速度快、界面友好
以下方案先采用第三种方案---Java applet,当年让Java 大火了一把的号称与微软的ActiveX 相提并论的技术当然,现在Java 出了JavaFX,是不是Applet 的替代品呢?你猜,猜不着呀?你再猜^_^