这里我们需要设置服务端的文件访问路径,我的默认是D:\FTP文件夹下。下载文件放在F:\FTPDOWNLOAD目录下。
以下是本程序所能接收的命令
ROOT -- 返回根目录
CWD -- 切换到下一级子目录
RCWD -- 转到上一级目录
LIST -- 列出当前目录下的所有文件信息
EXIT -- 退出
QUIT -- 退出
UPLOAD -- 上传文件
DOWNLOAD -- 下载文件
CWD 转向子目录
RCWD 回到上一级目录
ROOT 返回到根目录下
功能方面只实现了单文件的上传、下载、以及简单目录的切换
未实现登录模块、GUI图形化界面、权限控制、通过其他协议如http、https等等。
未实现的功能有:
大文件的传输
同名文件的检查
打包下载
文件夹的上传
不需要关闭防火墙
自动获取本机IP
端口冲突时选用备用端口
未有更加完善的错误处理机制等等
服务器端代码
import java.net.*;
/**
* @author 三文鱼
* @title
* @description
* @date 2022/7/14
**/
public class FtpServer extends Thread {
public static int ftpPort = 21;//定义服务器端口
ServerSocket serverSocket = null;
public static void main(String[] args) {
System.out.println("FTP地址为 D:\\FTP");
new FtpServer().start();
}
@Override
public void run() {
Socket socket;
try {
serverSocket = new ServerSocket(ftpPort);
System.out.println("开始监听的端口: " + ftpPort);
while(true){
//每一个新的连接 对应一个线程
socket = serverSocket.accept();
new FtpConnection(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
命令的处理
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author 三文鱼
* @title
* @description
* @date 2022/7/14
**/
public class FtpConnection extends Thread {
//通讯套接字
private Socket socket;
private BufferedReader reader = null;//请求的读取
private BufferedWriter writer = null;//响应的发送
private String clientIP;
public FtpConnection(Socket socket){
this.socket = socket;
//客户端ip
this.clientIP = socket.getInetAddress().getHostAddress();
}
public void run() {
String cmd ;
try {
System.out.println(clientIP + " connected! ");
//读取操作阻塞三秒 设置超时时间为5分钟
socket.setSoTimeout(300000);
//上传文件的输入、输出流
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(true){
System.out.println("监听命令中。。。");
//在这里被阻塞
cmd = reader.readLine();
System.out.println("命令为:" + cmd);
if(cmd.startsWith("QUIT") || cmd.startsWith("EXIT")) {
System.out.println("已断开连接。");
break;
}
handleCmd(cmd);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader!=null)reader.close();
} catch (Exception e){
e.printStackTrace();
}
try {
if (writer!=null)writer.close();
} catch (Exception e){
e.printStackTrace();
}
try {
if(this.socket!=null) socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//发送信息
private void response(String s) throws Exception {
writer.write(s);
writer.newLine();
//发送
writer.flush();
}
//命令的处理
private void handleCmd(String s) throws Exception {
if(s==null || s.equals(""))
return;
if(s.startsWith("CWD")) { // 设置当前目录,注意没有检查目录是否有效
String[] dir = s.split(" ");
String path = dir[1];
List<String> list = FileHelper.getAllFileInformation(path);
response(list.toString());
}
else if(s.startsWith("LIST")) { // 打印当前目录下所有文件
String[] params = s.split(",");
System.out.println("当前目录为: " + params[1]);
List<String> list = FileHelper.getAllFileInformation(params[1]);
response(list.toString());
}
else if(s.startsWith("UPLOAD")) {
//System.out.println("上传文件请求开始...");
//获取文件参数 构建文件及其输入流
String[] strings = s.split(",");
//----------这里要进行同名验证 如果文件已存在则需要进行同名验证 ---------
//连接客户端的IP套接字 默认为78接口传输数据
Socket fileSocket = new Socket(this.clientIP , 78);
//数据传输最多阻塞 一分钟
fileSocket.setSoTimeout(60000);
System.out.println("已连接到客户端");
//在这里发送一个连接成功的消息
// response("连接完成,开始传输数据。。。");
ArrayList<String> arrayList = new ArrayList<>();
//传输存储数据 执行循环直到文件传输完毕 ------ 文件过大时考虑分批次传输 ------
try(
BufferedInputStream inputStream = new BufferedInputStream(fileSocket.getInputStream());
//文件输入流
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File(strings[1])))
) {
while (inputStream.available() == 0) {
//直到有数据发送过来 一秒执行一次
System.out.println("等待传输接口发送数据...");
Thread.sleep(1000);
}
System.out.println("发现数据传来,开始接收。流大小为:" + inputStream.available());
int i = 0;
byte[] bytes = new byte[inputStream.available()];
while ((i = inputStream.read(bytes)) != -1) {
out.write(bytes , 0 , i);
}
out.flush();
System.out.println("上传已完成。");
arrayList.add("UPLOAD");
arrayList.add("上传成功。");
response(arrayList.toString());
}catch (IOException e) {
if(fileSocket != null) fileSocket.close();
response("ERROR,上传文件失败,遇到未知错误。");
}finally {
//关闭连接返回响应
if(fileSocket != null) fileSocket.close();
}
}
else if(s.startsWith("DOWNLOAD")) {
ArrayList<String> arrayList = new ArrayList<>();
//客户端的下载逻辑
String[] strings = s.split(",");
File file = new File(strings[1]);
//判断文件是否存在
if(!file.exists()) {
response("ERROR,文件不存在。");
} else if(!strings[1].contains("D:\\FTP")) {
response("ERROR,下载文件不合法。");
} else {
//以78接口传输下载文件,创建新的Socket,并且阻塞等待连接 --------------判断端口是否被占用,新建端口集合------------------
ServerSocket socketDownload = new ServerSocket(78);
Socket fileSocket = socketDownload.accept();
// response("连接已就绪。");
try(
//文件输入流
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
//套接字输出流
BufferedOutputStream out = new BufferedOutputStream(fileSocket.getOutputStream())
) {
int i = 0;
byte[] bytes = new byte[in.available()];
while ((i = in.read(bytes)) != -1) {
out.write(bytes ,0 , i);
}
out.flush();
System.out.println("数据发送完成。。。");
arrayList.add("DOWNLOAD");
arrayList.add("下载成功");
response(arrayList.toString());
}catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭连接
if(socketDownload != null) socketDownload.close();
}
}
}
else {
response("ERROR,没有匹配的命令。。。"); // 没有匹配的命令,输出错误信息
}
}
}
客户端代码
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* @author 三文鱼
* @title
* @description
* @date 2022/7/14
**/
public class FtpClient {
//当前访问目录
public static String currentPath = "D:\\FTP";
//通讯套接字
public static Socket socket = null;
//通讯的输入输出流
public static BufferedReader bufferedReader = null;
public static BufferedWriter bufferedWriter = null;
//服务端的IP
public static String serviceIP;
public static void main(String[] args) throws IOException {
//在这里设置服务器端的IP
serviceIP = "192.168.8.109";
try {
socket = new Socket(serviceIP , 21);
socket.setSoTimeout(60000);
System.out.println("连接成功。");
boolean sign = true;
Scanner scanner = new Scanner(System.in);
//用一个死循环阻塞
while (sign) {
System.out.println("请输入命令:");
String str = scanner.next();
//在这里进行判断,连接是否中断,如果连接已关闭 则重置连接
// System.out.println("重置连接。。");
// resetSocket();
//获取输入输出流 把这里的操作抽取出来 当输入的数据有错误 则返回错误信息 不进行通信
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
if(str.startsWith("EXIT") || str.startsWith("QUIT")) {
//跳出循环
request(bufferedWriter , str);
break;
} else if(str.startsWith("CWD")) {
//更改当前目录 - 请求是否存在该目录 - 不存在该目录则 操作失败
String[] strCwd = str.split(",");
setCurrentPath(currentPath + File.separator + strCwd[1]);
//传递给服务端的path 这里用空格区分命令和参数
str = "CWD " + currentPath;
request(bufferedWriter , str);
} else if(str.startsWith("RCWD")) {
//返回上一级目录
//至少包含根目录
if(getCurrentPath().contains("D:\\FTP")) {
String[] rStr = getCurrentPath().split("\\\\");
if(rStr.length <= 2) {
reSetPath();
str = "CWD " + currentPath;
} else {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < rStr.length-1; i++) {
if(i == rStr.length - 2) {
sb.append(rStr[i]);
}else {
sb.append(rStr[i]).append(File.separator);
}
}
setCurrentPath(sb.toString());
System.out.println("返回的目录: " + sb.toString());
str = "CWD " + sb.toString();
}
} else {
str = "ERROR";
}
request(bufferedWriter , str);
} else if(str.contains("ROOT")) {
//返回根目录
reSetPath();
str = "CWD " + currentPath;
request(bufferedWriter , str);
} else if (str.startsWith("UPLOAD")){
//上传逻辑 上传文件的位置 当前目录
String[] strings = str.split(",");
uploadFile(strings[1]);
} else if(str.startsWith("DOWNLOAD")) {
//下载逻辑 下载文件的路径 存储到本机的位置
String[] strings = str.split(",");
//下载逻辑
downloadFile(strings[1]);
} else if(str.startsWith("LIST")) {
request(bufferedWriter , "LIST," + currentPath);
}
//读出操作的返回信息
String response = bufferedReader.readLine();
//错误信息的处理
if(ifError(response))
System.out.println(getErrorMsg(response));
else
//对于命令的返回处理
handleRes(response);
}
System.out.println("连接关闭。");
}catch (IOException e) {
e.printStackTrace();
}finally {
if(socket!=null) socket.close();
if(bufferedWriter != null) bufferedWriter.close();
if(bufferedReader != null) bufferedWriter.close();
}
}
public static void request(BufferedWriter writer , String str) throws IOException {
writer.write(str);
writer.newLine();
//将数据发送
writer.flush();
}
//判断是否存在错误信息
public static boolean ifError(String res) {
if (res.contains("ERROR")) {
return true;
}else {
return false;
}
}
//获取错误信息
public static String getErrorMsg(String res) {
res = res.replace("[" , " ");
res = res.replace("]", " ");
res.trim();
String[] msg = res.split(",");
return msg[1];
}
public static void handleRes(String response) {
//数据传递以ArrayList传递
response = response.replace("[" , " ");
response = response.replace("]" , " ");
response.trim();
String[] resStr = response.split(",");
String cmd = resStr[0].toUpperCase().trim();
//list命令的返回
if("LIST".equals(cmd)) {
System.out.println(" 当前目录:" + currentPath);
for (int i = 1; i <= resStr.length - 1; i++) {
System.out.println(" " + resStr[i]);
}
} if("CWD".equals(cmd)) {
System.out.println(" 当前目录为: " + getCurrentPath());
for (int i = 1; i < resStr.length - 1; i++) {
System.out.println(" " + resStr[i]);
}
} if("UPLOAD".equals(cmd)) {
//返回操作成功的提示
System.out.println(resStr[1]);
}if("DOWNLOAD".equals(cmd)) {
//返回操作成功的提示
System.out.println(resStr[1]);
} else {
//如果是不存在的命令 就不予以发送 暂未完成
}
}
public static void uploadFile(String uploadPath) throws IOException {
System.out.println("上传文件路径为:" + uploadPath);
File file = new File(uploadPath);
if(!file.exists()) {
System.out.println("文件不存在。");
return;
}
if (!file.isFile()) {
System.out.println("无法上传文件夹,请确认。");
return;
}
//用于传输数据的套接字
//System.out.println("新建的套接字连接");
ServerSocket fileSocket = new ServerSocket(78);
fileSocket.setSoTimeout(60000);
//通信Socket输入输出流
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//告知服务器端开始连接78端口 目录及文件名称 1
//System.out.println("发送真正的请求:");
request(bufferedWriter , "UPLOAD," +getCurrentPath() +File.separator + file.getName());
//等待服务器连接
Socket uploadFileSocket = fileSocket.accept();
//连接成功显示 2
//System.out.println("连接成功");
//如果连接成功 则收取服务端的消息
// System.out.println(bufferedReader.readLine());
try(
//文件输入流
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
//Socket输出流
//考虑使用新的Socket创建连接实现文件的传输
BufferedOutputStream outputStream = new BufferedOutputStream(uploadFileSocket.getOutputStream());
) {
// System.out.println("流大小为:" + inputStream.available());
byte[] bytes = new byte[inputStream.available()];
int i = 0;
while ((i = inputStream.read(bytes)) != -1) {
outputStream.write(bytes , 0 , i);
}
//一次发送完所有数据
outputStream.flush();
//连接套接字的返回的传输完成信息
//System.out.println(bufferedReader.readLine());
}catch (IOException e) {
e.printStackTrace();
} finally {
if(fileSocket != null) fileSocket.close();
if(uploadFileSocket != null) uploadFileSocket.close();
}
}
public static void downloadFile(String fileName) throws IOException {
//拼接文件的下载地址
String filePath = getCurrentPath() + File.separator + fileName;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getSocket().getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(getSocket().getOutputStream()));
//下载命令的发送
request(bufferedWriter , "DOWNLOAD," + filePath);
//合法文件 连接对应的接口
Socket socketDownload = new Socket(serviceIP , 78);
//数据传输时间不得超过一分钟
socketDownload.setSoTimeout(60000);
//System.out.println(bufferedReader.readLine());
//默认存储路径
String storePath = "F:" + File.separator + "FTPDOWNLOAD" + File.separator + fileName;
System.out.println("存储路径为:" + storePath);
File file = new File(storePath);
if(file.exists())
file.delete();
try(
//将文件存储到本地 默认为F:\FTPDOWNLOAD
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
BufferedInputStream in = new BufferedInputStream(socketDownload.getInputStream())
) {
while (in.available() == 0) {
Thread.sleep(1000);
System.out.println("等待服务器端发送数据...");
}
System.out.println("文件流大小为:" + in.available());
int i = 0;
byte[] bytes = new byte[in.available()];
while ((i = in.read(bytes)) != -1) {
out.write(bytes , 0 , i);
}
out.flush();
}catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(socketDownload != null) socketDownload.close();
}
}
//重置连接
public static void resetSocket() throws IOException {
Socket socket1 = new Socket(serviceIP , 21);
socket1.setSoTimeout(300000);
setSocket(socket1);
System.out.println("连接已经重置。。。");
}
//获取当前目录
public static String getCurrentPath() {
return currentPath;
}
//设置目录
public static void setCurrentPath(String path) {
currentPath = path;
}
//返回根节点
public static void reSetPath() {
currentPath = "D:\\FTP";
}
//获取当前的通信套接字
public static Socket getSocket() {
return socket;
}
//设置通信的套接字
public static void setSocket(Socket socket) {
FtpClient.socket = socket;
}
}
用于读取指定目录下的所有文件
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @author 三文鱼
* @title
* @description
* @date 2022/7/14
**/
public class FileHelper {
//默认文件夹下
public static String filePath = "D:\\FTP";
public static List<String> getAllFileInformation(String path) {
if(path == null)
path = filePath;
List<String> list = new ArrayList<>();
//确保只能在固定文件夹下进行操作
if (!path.contains(filePath)) {
System.out.println(path);
addErrorInformation(list , "访问目录不合法。");
}
File file = new File(path);
if (file.isFile()) {
addErrorInformation(list , "访问的是文件。请确认。");
}
if (!file.exists()) {
addErrorInformation(list , "目录不存在。请确认。");
}else {
list.add("LIST");
File[] files = file.listFiles();
for (File f : files) {
String name = f.getName();//文件名称
System.out.println(" 文件名为:" + name);
StringBuilder stringBuilder = new StringBuilder();
if(f.isFile()) {
String sizeStr = null;
long size = f.length();
if (size/1024 != 0) {
sizeStr = String.valueOf(size/1024) + "kb";
}else {
sizeStr = String.valueOf(size) + "b";
}
String fileStr = name + sizeStr;
stringBuilder.append(name);
stringBuilder.append(" ").append(" ");
stringBuilder.append(sizeStr);
fileStr = stringBuilder.toString();
list.add(fileStr);
}else {
//目录
list.add(name);
}
}
}
return list;
}
//
public static void addErrorInformation(List<String> list , String str) {
if(list == null) {
return;
}
list.add("ERROR");
list.add(str);
}
}
只有自己进行通信的时候才能明白格式化有多么的重要,哈哈哈,不然响应返回的数据,处理起来真是心态爆炸,也让我明白为什么要用统一的JSON格式传递数据了。自己搭建一个简单的FTP并不困难,用到的主要技术大概有Thread、Stream、Socket等等,但是要耐心地分析个个功能。