Java使用Socket简单实现FTP

Java使用Socket简单实现FTP

  • 简单实现的FTP运行结果
    • 必要的前置条件
      • 服务器端的目录结构
      • 客户端存储文件的位置
    • 测试命令
    • 结果
      • LIST、QUIT、EXIT
      • CWD、RCWD、ROOT
      • DOWNLOAD、UPLOAD
        • UPLOAD 上传文件
        • DOWNLOAD 下载文件
    • 本程序的不足以及未来的方向
      • 功能方面
      • 可用性
  • 实现代码
    • FtpServer
    • FtpConnection
    • FtpClient
    • FileHelper
  • 写在最后

简单实现的FTP运行结果

必要的前置条件

这里我们需要设置服务端的文件访问路径,我的默认是D:\FTP文件夹下。下载文件放在F:\FTPDOWNLOAD目录下。

服务器端的目录结构

Java使用Socket简单实现FTP_第1张图片
Java使用Socket简单实现FTP_第2张图片
Java使用Socket简单实现FTP_第3张图片

客户端存储文件的位置

Java使用Socket简单实现FTP_第4张图片

测试命令

以下是本程序所能接收的命令

ROOT     -- 返回根目录
CWD      -- 切换到下一级子目录
RCWD     -- 转到上一级目录
LIST     -- 列出当前目录下的所有文件信息
EXIT     -- 退出
QUIT     -- 退出
UPLOAD   -- 上传文件
DOWNLOAD -- 下载文件

结果

LIST、QUIT、EXIT

LIST 列出当前目录下的文件
Java使用Socket简单实现FTP_第5张图片
QUIT、EXIT 退出
Java使用Socket简单实现FTP_第6张图片
Java使用Socket简单实现FTP_第7张图片

CWD、RCWD、ROOT

CWD 转向子目录
Java使用Socket简单实现FTP_第8张图片
RCWD 回到上一级目录
Java使用Socket简单实现FTP_第9张图片
ROOT 返回到根目录下
Java使用Socket简单实现FTP_第10张图片

DOWNLOAD、UPLOAD

UPLOAD 上传文件

Java使用Socket简单实现FTP_第11张图片
FTP服务文件的存储
Java使用Socket简单实现FTP_第12张图片

DOWNLOAD 下载文件

Java使用Socket简单实现FTP_第13张图片
本机的默认存储位置
在这里插入图片描述

本程序的不足以及未来的方向

功能方面

功能方面只实现了单文件的上传下载、以及简单目录的切换
未实现登录模块GUI图形化界面权限控制通过其他协议如http、https等等。

可用性

未实现的功能有:
大文件的传输
同名文件的检查
打包下载
文件夹的上传
不需要关闭防火墙
自动获取本机IP
端口冲突时选用备用端口
未有更加完善的错误处理机制等等

实现代码

FtpServer

服务器端代码

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();
        }
    }

}

FtpConnection

命令的处理

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,没有匹配的命令。。。"); // 没有匹配的命令,输出错误信息
        }
    }

}

FtpClient

客户端代码

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;
    }
}

FileHelper

用于读取指定目录下的所有文件

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并不困难,用到的主要技术大概有ThreadStreamSocket等等,但是要耐心地分析个个功能。

你可能感兴趣的:(奇奇怪怪的东西,java,服务器,linux)