Java使用Socket简单实现FTP

简单实现的FTP运行结果

必要的前置条件

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

服务器端的目录结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

客户端存储文件的位置

在这里插入图片描述

测试命令

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

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

结果

LIST、QUIT、EXIT

LIST 列出当前目录下的文件
在这里插入图片描述
QUIT、EXIT 退出
在这里插入图片描述
在这里插入图片描述

CWD、RCWD、ROOT

CWD 转向子目录
在这里插入图片描述
RCWD 回到上一级目录
在这里插入图片描述
ROOT 返回到根目录下
在这里插入图片描述

DOWNLOAD、UPLOAD

UPLOAD 上传文件

在这里插入图片描述
FTP服务文件的存储
在这里插入图片描述

DOWNLOAD 下载文件

在这里插入图片描述
本机的默认存储位置
在这里插入图片描述

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

功能方面

功能方面只实现了单文件的上传下载、以及简单目录的切换
未实现登录模块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等等,但是要耐心地分析个个功能。

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
在上一篇文章中,我们学习了如何使用 Socket 建立 FTP 连接并发送 FTP 命令。本篇文章将介绍如何使用 Socket 实现 FTP 客户端程序的文件传输功能。 1. 传输模式 FTP 有两种传输模式:ASCII 模式和二进制模式。ASCII 模式适用于传输文本文件,文件内容会被转换为 ASCII 码。二进制模式适用于传输二进制文件,文件内容不会被转换。在 FTP 服务器和客户端之间进行传输时,需要指定传输模式。 在 Java 中,我们可以使用 InputStream 和 OutputStream 实现文件的二进制传输,使用 InputStreamReader 和 OutputStreamWriter 实现文件的文本传输。在传输文件之前,我们需要先使用 TYPE 命令指定传输模式。 2. 下载文件 下载文件需要使用 RETR 命令FTP 服务器获取文件内容,并将获取的内容写入本地文件中。具体实现步骤如下: (1)使用 PASV 命令进入被动模式,获取 FTP 服务器开放的数据端口号和 IP 地址。 (2)使用 RETR 命令获取文件内容。 (3)将获取到的文件内容写入本地文件。 代码如下: ```java // 进入被动模式 sendCommand("PASV"); String response = readResponse(); String[] pasv = response.substring(response.indexOf("(") + 1, response.indexOf(")")).split(","); String ip = pasv[0] + "." + pasv[1] + "." + pasv[2] + "." + pasv[3]; int port = Integer.parseInt(pasv[4]) * 256 + Integer.parseInt(pasv[5]); // 获取文件内容并写入本地文件 Socket dataSocket = new Socket(ip, port); sendCommand("RETR " + remoteFilePath); InputStream inputStream = dataSocket.getInputStream(); FileOutputStream fileOutputStream = new FileOutputStream(localFilePath); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { fileOutputStream.write(buffer, 0, length); } fileOutputStream.close(); inputStream.close(); dataSocket.close(); ``` 3. 上传文件 上传文件需要使用 STOR 命令FTP 服务器发送文件内容。具体实现步骤如下: (1)使用 PASV 命令进入被动模式,获取 FTP 服务器开放的数据端口号和 IP 地址。 (2)使用 STOR 命令FTP 服务器发送文件内容。 代码如下: ```java // 进入被动模式 sendCommand("PASV"); String response = readResponse(); String[] pasv = response.substring(response.indexOf("(") + 1, response.indexOf(")")).split(","); String ip = pasv[0] + "." + pasv[1] + "." + pasv[2] + "." + pasv[3]; int port = Integer.parseInt(pasv[4]) * 256 + Integer.parseInt(pasv[5]); // 向 FTP 服务器发送文件内容 Socket dataSocket = new Socket(ip, port); sendCommand("STOR " + remoteFilePath); OutputStream outputStream = dataSocket.getOutputStream(); FileInputStream fileInputStream = new FileInputStream(localFilePath); byte[] buffer = new byte[1024]; int length; while ((length = fileInputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } fileInputStream.close(); outputStream.close(); dataSocket.close(); ``` 4. 总结 本篇文章介绍了使用 Socket 实现 FTP 客户端程序的文件传输功能。通过掌握这些知识,我们可以轻松地实现 FTP 客户端程序的文件传输功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值