文章目录
S-FTPClient
S-FTPClient(Swing,Java,Socket,FTP,加密算法)
一、 问题描述
- 实现一个图形用户界面的FTP客户端,保证文件的安全传输和存储。
- 客户端能够发出各种操作命令;
- 实现:
conn(连接)
、list(列示文件)
、retr(下载)
、store(上载)
的功能;- 使用一种
加密算法
,在文件上载前进行加密
,文件
以密文的形式传输
和保存
在FTP服务器上,设计客户端的密钥管理机制
。
二、基本要求
-
1、实现一个图形用户界面的FTP客户端,保证文件的安全传输和存储。
-
2、功能:
- 2.1 配置使用IIS的FTP服务器;
- 2.2 客户端发出各种操作命令;至少
实现conn(连接)
、list(列示文件)
、retr(下载)
、store(上载)
的功能;
-
3、选择你学过的
加密算法
,在文件上载前进行加密
,文件以密文
的形式传输和保存在FTP服务器上,设计客户端的密钥管理机制。 -
4、接收服务器的操作结果,如显示连接状态,对下载的文件进行解密等。
-
5、用户界面:
客户端界面用户可以设置远程主机名
、用户和密码
;显示远程文件列表
;显示本地文件的列表
;操作命令可以采用菜单
、按钮及弹出菜单来实现
;显示操作状态
(操作是否成功
、状态
、文件操作的进度
等)。
三、设计思想
FTP客户端是建立在Java的Swing技术上,首先设计好大概的FTP客户端的界面模型,然后利用Java的Swing技术绘制好FTP客户端的界面,最后利用Socket技术设计操作FTP客户端的命令等。
用户可以通过它把自己机器与世界各地所有运行 FTP协议的服务器相连,访问服务器上的资源和信息。
当启动 FTP 从远程计算机拷贝文件时,事实上启动了两个程序:一个本地机器上FTP客户端程序,它向 FTP服务器提出拷贝文件的请求。另一个是启动在远程计算机的上的 FTP服务器程序,它响应请求把你指定的文件传送到你的计算机中。
在典型的 FTP 会话过程中,用户一般在本地主机前进行同远程主机之间的文件传输。
为了能够访问远程账户,用户必须提供用户标识和密码。在通过了身份验证之后,用户就可以在本地主机和远程主机之间传输文件了。用户通过 FTP 的用户代理与 FTP 进行交互。用户首先需要远程提供主机名或 IP 地址,以便本地 FTP的客户进程能够同远程主机上的 FTP服务器进程建立连接。然后,用户提供其标识和密码。一旦验证通过,用户即可在两个系统之间传输文件。
FTP使用两个并行的 TCP协议来传输文件,一个称为控制连接,另一个称为数据连接。控制连接用来在两台主机之间传输控制信息,如用户标识、密码、操作远程主机文件目录的命令、发送和取向文件的命令等。而数据连接则真正用来发送文件。
FTP的控制和数据连接如下图所示。
当用户启动一次与远程主机的 FTP会话时,FTP首先建立一个 TCP连接到 FTP服务器的 21号端口。
FTP的客户端则通过该连接发送用户标识和密码等,客户端还可以通过该连接发送命令以改变远程系统的当前工作目录。当用户要求传送文件时,FTP服务器则在其 20号端口上建立一个数据连接,FTP在该连接上传送完毕一个文件后会立即断开该连接。
如果再一次 FTP会话过程中需要传送另一个文件,FTP服务器则会建立另一个连接。在整个 FTP会话过程中,控制连接是始终保持的,而数据连接则会随着文件的传输不断的打开和关闭。
综上所述需要根据FTP的控制连接和数据连接这整个流程完成对FTP的操作。
四、系统结构
经过需求分析后,决定此FTP客户端分本地文件加载系统模块、连接数据模块、断开连接模块、下载模块、刷新模块、删除模块等几项关键的模块。
本程序的系统结构图如下:
五、FTP客户端结构
(一)本地文件系统加载模块:
当页面初始化的时候用来加载本地文件系统的,可以让用户更为直观的,方便的,快捷的选择本地相应的文件进行浏览、上传等操作。本地文件系统加载模块如图所示:
(二)本地文件系统加载模块
连接模块:
连接模块是在图形界面下用户交互与FTP服务器建立连接的一个核心的模块功能。可以使用规定好的IP地址进行连接,IP无论是外网还是内网都可以进行连接。支持不同端口的FTP服务器进行连接,这个设计是为了方便不同端口的FTP。支持匿名用户和FTP普通用户登录客户端。全称由用户决定,随时可以输入的。连接模块如图所示:
断开模块:
断开模块是方便用户断开已有的连接而设计的。此功能图和连接模块的图一样。
下载模块:
下载模块是为用户下载FTP服务器的文件而设计的。当用户想要下载的时候,必须要选择上想要下载的文件。不然是不能下载的,界面设计如下:
刷新、删除模块如上图所示。
(三)程序流程
本系统主要的流程如下:
从运行程序到登录FTP服务器到拉取FTP服务器目录
上传文件流程图:
下载数据流程图:
加密流程:
解密过程:
(四)测试
1、测试环境
使用IIS搭建的本地的FTP服务器。
2、测试数据
第一种:
只输入FTP服务器的连接地址:127.0.0.1
第二种:
输入地址:127.0.0.1
端口:21
账号:zhenghui
密码:8042965
然后进行上传本地的文件和下载FTP服务器上的文件
(五)测试情况
第一种情况:
选择文件上传
下载:
下载到特定位置:
下载后的文件:
选择一个进行删除
删除后会立马刷新列表
六、核心代码
(一)初始化ftp服务器
/**
* 初始化ftp服务器
*/
public void initFtpClient() {
System.out.println(hostname+" ," +port+","+username+","+password);
ftpClient = new FTPClient();
// ftpClient.setControlEncoding("utf-8");
ftpClient.setControlEncoding("GBK");
try {
System.out.println("connecting...ftp服务器:" + this.hostname + ":" + this.port);
ftpClient.connect(hostname, port); // 连接ftp服务器
ftpClient.login(username, password); // 登录ftp服务器
int replyCode = ftpClient.getReplyCode(); // 是否成功登录服务器
if (!FTPReply.isPositiveCompletion(replyCode)) {
System.out.println("connect failed...ftp服务器:" + this.hostname + ":" + this.port);
}
System.out.println("connect successfu...ftp服务器:" + this.hostname + ":" + this.port);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
(二)上传文件
/**
* 上传文件
*
* @param pathname
* ftp服务保存地址
* @param fileName
* 上传到ftp的文件名
* @param originfilename
* 待上传文件的名称(绝对地址) *
* @return
*/
public boolean uploadFile(String pathname, String fileName, String originfilename) {
InputStream inputStream = null;
try {
System.out.println("开始上传文件");
inputStream = new FileInputStream(new File(originfilename));
initFtpClient();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
CreateDirecroty(pathname);
ftpClient.makeDirectory(pathname);
ftpClient.changeWorkingDirectory(pathname);
// 每次数据连接之前,ftp client告诉ftp server开通一个端口来传输数据
ftpClient.enterLocalPassiveMode();
// 观察是否真的上传成功
boolean storeFlag = ftpClient.storeFile(fileName, inputStream);
System.err.println("storeFlag==" + storeFlag);
inputStream.close();
ftpClient.logout();
System.out.println("上传文件成功");
} catch (Exception e) {
System.out.println("上传文件失败");
e.printStackTrace();
} finally {
if (ftpClient.isConnected()) {
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
/**
* 上传文件
*
* @param pathname
* ftp服务保存地址
* @param fileName
* 上传到ftp的文件名
* @param inputStream
* 输入文件流
* @return
*/
public boolean uploadFile(String pathname, String fileName, InputStream inputStream) {
try {
System.out.println("开始上传文件");
initFtpClient();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
CreateDirecroty(pathname);
ftpClient.makeDirectory(pathname);
ftpClient.changeWorkingDirectory(pathname);
ftpClient.storeFile(fileName, inputStream);
inputStream.close();
ftpClient.logout();
System.out.println("上传文件成功");
} catch (Exception e) {
System.out.println("上传文件失败");
e.printStackTrace();
} finally {
if (ftpClient.isConnected()) {
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
(三)切换目录
// 改变目录路径
public boolean changeWorkingDirectory(String directory) {
boolean flag = true;
try {
flag = ftpClient.changeWorkingDirectory(directory);
if (flag) {
System.out.println("进入文件夹" + directory + " 成功!");
} else {
System.out.println("进入文件夹" + directory + " 失败!开始创建文件夹");
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
return flag;
}
(四)切换目录
// 创建多层目录文件,如果有ftp服务器已存在该文件,则不创建,如果无,则创建
public boolean CreateDirecroty(String remote) throws IOException {
boolean success = true;
String directory = remote + "/";
// 如果远程目录不存在,则递归创建远程服务器目录
if (!directory.equalsIgnoreCase("/") && !changeWorkingDirectory(new String(directory))) {
int start = 0;
int end = 0;
if (directory.startsWith("/")) {
start = 1;
} else {
start = 0;
}
end = directory.indexOf("/", start);
String path = "";
String paths = "";
while (true) {
String subDirectory = new String(remote.substring(start, end).getBytes("GBK"), "iso-8859-1");
path = path + "/" + subDirectory;
if (!existFile(path)) {
if (makeDirectory(subDirectory)) {
changeWorkingDirectory(subDirectory);
} else {
System.out.println("创建目录[" + subDirectory + "]失败");
changeWorkingDirectory(subDirectory);
}
} else {
changeWorkingDirectory(subDirectory);
}
paths = paths + "/" + subDirectory;
start = end + 1;
end = directory.indexOf("/", start);
// 检查所有目录是否创建完毕
if (end <= start) {
break;
}
}
}
return success;
}
(五)判断ftp服务器文件是否存在
// 判断ftp服务器文件是否存在
public boolean existFile(String path) throws IOException {
boolean flag = false;
FTPFile[] ftpFileArr = ftpClient.listFiles(path);
if (ftpFileArr.length > 0) {
flag = true;
}
return flag;
}
(六)创建目录
// 创建目录
public boolean makeDirectory(String dir) {
boolean flag = true;
try {
flag = ftpClient.makeDirectory(dir);
if (flag) {
System.out.println("创建文件夹" + dir + " 成功!");
} else {
System.out.println("创建文件夹" + dir + " 失败!");
}
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
(七)下载文件
/**
* * 下载文件 *
*
* @param pathname
* FTP服务器文件目录 *
* @param filename
* 文件名称 *
* @param localpath
* 下载后的文件路径 *
* @return
*/
public boolean downloadFile(String pathname, String filename, String localpath) {
boolean flag = false;
OutputStream os = null;
try {
initFtpClient();
// 切换FTP目录
boolean changeFlag = ftpClient.changeWorkingDirectory(pathname);
ftpClient.enterLocalPassiveMode();
ftpClient.setRemoteVerificationEnabled(false);
// 查看有哪些文件夹 以确定切换的ftp路径正确
String[] a = ftpClient.listNames();
FTPFile[] ftpFiles = ftpClient.listFiles();
for (FTPFile file : ftpFiles) {
if (filename.equalsIgnoreCase(file.getName())) {
byte[] base64jiemi = ZHFileBase64Key.base64jiemi(file.getName());
String jiemiFileName = new String(base64jiemi);
String[] split = jiemiFileName.split("#SDJZDX_zhenghuiagsdahscasdqwFTP");
File localFile = new File("/" + split[0]);
os = new FileOutputStream(localFile);
ftpClient.retrieveFile(file.getName(), os);
os.close();
ZHFileCipherTxst.decode("/" + split[0], localpath+"/" + split[0], ZHFileCipherTxst.key);
localFile.delete();
}
}
ftpClient.logout();
flag = true;
System.out.println("下载文件成功");
} catch (Exception e) {
System.out.println("下载文件失败");
e.printStackTrace();
} finally {
if (ftpClient.isConnected()) {
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != os) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return flag;
}
(八)删除文件
/**
* * 删除文件 *
*
* @param pathname
* FTP服务器保存目录 *
* @param filename
* 要删除的文件名称 *
* @return
*/
public boolean deleteFile(String pathname, String filename) {
boolean flag = false;
try {
System.out.println("开始删除文件");
initFtpClient();
// 切换FTP目录
ftpClient.changeWorkingDirectory(pathname);
ftpClient.dele(filename);
ftpClient.logout();
flag = true;
System.out.println("删除文件成功");
} catch (Exception e) {
System.out.println("删除文件失败");
e.printStackTrace();
} finally {
if (ftpClient.isConnected()) {
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return flag;
}
(九)连接ftp服务器
//连接ftp服务器
public void connect(String host, int port, String userName, String password){
try {
//与ftp建立连接
socket = new Socket(host,port);
//读
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//写
// writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
System.out.println("正在连接" + host+"....");
mainForm.setFTPMessage("正在连接" + host+"....");
ctrlWriter = new PrintWriter(socket.getOutputStream());
String line = null;
line = reader.readLine();
System.out.println("响应:"+line);
mainForm.setFTPMessage("响应:"+line);
//发送命令,输入用户名:
System.out.println("命令:USER "+userName);
mainForm.setFTPMessage("命令:USER "+userName);
sendCommand("USER "+userName);
line = reader.readLine();
System.out.println("响应:"+line);
mainForm.setFTPMessage("响应:"+line);
//发送命令,输入密码
sendCommand("PASS "+password);
line = reader.readLine();
System.out.println("命令:PASS "+password);
System.out.println("响应:"+line);
mainForm.setFTPMessage("命令:PASS "+password);
mainForm.setFTPMessage("响应:"+line);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
(十)操作FTP的命令
//输入操作FTP的命令
public void sendCommand(String com) throws IOException {
//先判断是否连接了
if(socket == null) {
throw new IOException("FTP未连接");
}
//如果发送失败
try {
//发送命令
// writer.write(com+"\r\n");
// writer.flush();
ctrlWriter.println(com);
ctrlWriter.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
(十一)查询ftp服务器所有文件
//查询所有的文件
public void selectAll() throws IOException {
if(socket == null) {
throw new IOException("FTP未连接");
}
//如果发送失败
try {
//发送命令
sendCommand("PWD");
sendCommand("PASV");
sendCommand("TYPE A");
sendCommand("LIST");
String line = null;
while ((line=reader.readLine())!=null) {
System.out.println(line);
}
} catch (Exception e) {
socket = null;
throw e;
}
}
(十二)向ftp服务器发送指令
//发送指令
public void sendOrder(String order) throws IOException {
sendCommand("PASV");//开启一个被动连接的
// sendCommand("PORT");//开启一个主动连接的
//取得被动连接模式的端口等信息
String response = reader.readLine();
System.out.println(response);
int opening = response.indexOf('(');
int closing = response.indexOf(')', opening + 1);
String ip_port = response.substring(opening+1,closing);
// System.out.println(closing);
//提取227 Entering Passive Mode (127,0,0,1,250,80). 中的IP地址和端口号
//IP:127.0.0.1
//端口:250*254+80
String ip = null;
int port = -1;
if (closing > 0) {
String dataLink = response.substring(opening + 1, closing);
StringTokenizer tokenizer = new StringTokenizer(dataLink, ",");
try {
ip = tokenizer.nextToken() + "." + tokenizer.nextToken() + "." + tokenizer.nextToken() + "." + tokenizer.nextToken();
port = Integer.parseInt(tokenizer.nextToken()) * 256 + Integer.parseInt(tokenizer.nextToken());
} catch (Exception e) {
throw new IOException("FTP接收到错误的数据链接信息: "
+ response);
}
}
System.out.println("上传文件的IP:"+ip + " 端口号:" + port);
mainForm.setFTPMessage("上传文件的IP:"+ip + " 端口号:" + port);
sendCommand("PORT "+ip_port);//发送命令
System.out.println(reader.readLine());
sendCommand(order);//发送命令
String str1 = null;
while ((str1=reader.readLine())!=null) {
System.out.println("str1:"+str1);
}
}
//dataConnection方法
//构造与服务器交换数据用的Socket
//再用PORT命令将端口通知服务器
public Socket dataConnection(String ctrlcmd,int port) {
String cmd = "PORT "; // PORT存放用PORT命令传递数据的变量
int i;
Socket dataSocket = null;// 传送数据用Socket
try {
// 得到自己的地址
byte[] address = InetAddress.getLocalHost().getAddress();
// 用适当的端口号构造服务器
ServerSocket serverDataSocket = new ServerSocket(port,1);
// 利用控制用的流传送PORT命令
ctrlWriter.println(cmd+""+port);
ctrlWriter.flush();
// 向服务器发送处理对象命令(LIST,RETR,及STOR)
ctrlWriter.println(ctrlcmd);
ctrlWriter.flush();
// 接受与服务器的连接
dataSocket = serverDataSocket.accept();
serverDataSocket.close();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
return dataSocket;
}
(十三)获取FTP的数据
//获取FTP的数据
public void listFiles(String string, FTPTableModel ftpTableModel) throws IOException {
sendCommand("cwd " + string);
String string1 = reader.readLine();
System.out.println("响应:" + string1);
mainForm.setFTPMessage("响应:"+ string1);
sendCommand("PASV");
//取得目录信息
String response = reader.readLine();
String[] ip_PORT = getIP_PORT(response);
String ip =ip_PORT[0];
int port = Integer.valueOf(ip_PORT[1]);
System.out.println("listFilesIP:"+ip + " listFiles端口号:" + port);
sendCommand("LIST" );
mainForm.setFTPMessage("listFilesIP:"+ip + " listFiles端口号:" + port);
Socket dataSocket = new Socket(ip, port);
DataInputStream dis = new DataInputStream(dataSocket.getInputStream());
String s = "";
ArrayList<FileEntity> fileList = new ArrayList<FileEntity>();
//遍历目录
while ((s = dis.readLine()) != null) {
String l = new String(s.getBytes("ISO-8859-1"), "GB2312");//转换字符类型,解决乱码问题
//如果是目录
if(l.indexOf("<DIR>")!=-1) {
//是目录的时候的字符串分割,拿到文件名的
String[] split = l.split(" <DIR>");
String string2 = "日期:"+split[0]+",文件夹,文件名:"+split[1].split(" ")[1];
String fileName = split[1].split(" ")[1];
String fileSise = "-1";
String fileType = "文件夹";
String fileUpdateDate = split[0];
//存到List里
fileList.add(new FileEntity(fileName, fileSise, fileType, fileUpdateDate));
}else{//如果不是目录
String[] split = l.split(" ");
String[] split2 = split[1].trim().split(" ");
String string2 ="日期:"+split[0]+",文件,大小:"+split2[0]+",文件名:"+split2[1];
// mainForm.setFTPMessage(string2);
String fileName = split2[1];
String fileSise = String.valueOf(split2[0]);
String fileType = "文件";
String fileUpdateDate = split[0];
//存到List里
fileList.add(new FileEntity(fileName, fileSise, fileType, fileUpdateDate));
}
}
ftpTableModel.setData(fileList);
dis.close();
dataSocket.close();
System.out.println(reader.readLine());
System.out.println(reader.readLine());
// String str1 = null;
// while ((str1=reader.readLine())!=null) {
// }
}
(十四)提取IP地址和端口号
//提取IP地址和端口号
public String[] getIP_PORT(String response) throws IOException {
String ip = null;
int port = -1;
int opening = response.indexOf('(');
int closing = response.indexOf(')', opening + 1);
if (closing > 0) {
String dataLink = response.substring(opening + 1, closing);
StringTokenizer tokenizer = new StringTokenizer(dataLink, ",");
try {
ip = tokenizer.nextToken() + "." + tokenizer.nextToken() + "."
+ tokenizer.nextToken() + "." + tokenizer.nextToken();
port = Integer.parseInt(tokenizer.nextToken()) * 256
+ Integer.parseInt(tokenizer.nextToken());
} catch (Exception e) {
throw new IOException("FTP接收到错误的数据链接信息: " + response);
}
}
String [] str= {ip,String.valueOf(port)};
return str;
}
(十五)关闭所有连接
//关闭所有的连接
public void closeAll() {
if(socket!=null) {
try {
socket.close();
} catch (IOException e) {
System.out.println("关闭socket失败");
e.printStackTrace();
}
}
if(reader!=null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(ctrlWriter!=null) {
ctrlWriter.close();
}
}
七、项目下载
项目放在:
下载项目点我