[Java] FTP에 접속하여 파일 다운로드, 업로드하는 방법(FTPClient)


Development note/Java  2020. 2. 18. 09:00

안녕하세요. 명월입니다.


이 글은 Java에서 FTP에 접속하여 파일 다운로드, 업로드하는 방법(FTPClient)에 대한 글입니다.


이전에 제가 Window환경에서 FTP Server를 만드는 방법에 대하 소개한 적이 있습니다.

링크 - [CentOS] FTP 설정, vsftpd 설정

링크 - [Window] FTP 서버를 구축하는 방법


FTP 프로토콜을 이전 만큼은 아니지만, 여러가지 파일 전송 프로토콜로써 아직 사용하는 곳이 있기에 소개하겠습니다.

C#으로 FTP 클라이언트를 설명했었는데 비슷한 흐름으로 작성하겠습니다.

링크 - [C#] FTP에 접속해서 파일 다운로드, 업로드하는 방법


먼저 Java 환경에서 FTP를 사용하기 위해서는 라이브러리를 다운 받아야 합니다.

레포지토리 - https://mvnrepository.com/artifact/commons-net/commons-net/3.6

<!-- https://mvnrepository.com/artifact/commons-net/commons-net -->
<dependency>
  <groupId>commons-net</groupId>
  <artifactId>commons-net</artifactId>
  <version>3.6</version>
</dependency>

FTP Client 환경을 꼭 웹에서 사용하는 것만은 아니기 때문에 Console로 작성하겠습니다.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

public class Program {
  // 시작 함수
  public static void main(String[] args) {
    // FTP를 접속하기 위한 클래스를 선언한다.
    FTPClient client = new FTPClient();
    try {
      // connection 환경에서 UTF-8의 인코딩 타입을 사용한다.
      client.setControlEncoding("UTF-8");
      // ftp://localhost에 접속한다.
      client.connect("localhost", 21);
      // 접속을 확읺나다.
      int resultCode = client.getReplyCode();
      // 접속시 에러가 나오면 콘솔에 에러 메시지를 표시하고 프로그램을 종료한다.
      if (!FTPReply.isPositiveCompletion(resultCode)) {
        System.out.println("FTP server refused connection.!");
        return;
      } else {
        // 파일 전송간 접속 딜레이 설정 (1ms 단위기 때문에 1000이면 1초)
        client.setSoTimeout(1000);
        // 로그인을 한다.
        if (!client.login("FTPUser", "password")) {
          // 로그인을 실패하면 프로그램을 종료한다.
          System.out.println("Login Error!");
          return;
        }

        ///************FTP 모두 삭제***************
        // 파일 정보를 위한 리스트 변수
        List<String> files = new ArrayList<>();
        // 디렉토리 정보를 위한 리스트 변수
        List<String> directories = new ArrayList<>();
        // FTP에서 파일 리스트와 디렉토리 정보를 취득한다.
        if (getFileList(client, File.separator, files, directories)) {
          // 모든 파일을 지운다.
          for (String file : files) {
            client.deleteFile(file);
          }
          // getFileList에서 재귀적 함수를 이용하여 디렉토리를 구조를 구하였다.
          // 그래서 상위 디렉토리 정보가 리스트의 앞쪽에 있고 하위 디렉토리 정보가 리스트 뒤쪽에 있다.
          // 디렉토리 삭제는 하위부터 삭제해야 하기 때문에 리스트를 뒤집은 것
          Collections.reverse(directories);
          // 디렉토리를 지운다.
          for (String directory : directories) {
            client.removeDirectory(directory);
          }
        } else {
          // 리스트 취득 실패시 프로그램을 종료한다.
          System.out.println("File search Error!");
          return;
        }

        ///************업로드***************
        // 로컬 디렉토리 설정
        String root = "d:\\ftptest\\upload";
        // 리스트 초기화
        files.clear();
        directories.clear();
        // 로컬 디렉토리 파일과 디렉토리 정보를 취득
        getUploadList("d:\\ftptest\\upload", files, directories);
        // 디렉토리 생성
        for (String directory : directories) {
          client.makeDirectory(directory);
        }
        // 파일 업로드
        for (String file : files) {
          // 파일 InputStream을 가져온다.
          try (FileInputStream fi = new FileInputStream(file)) {
            // FTPClient의 staoreFile함수로 보내면 업로드가 이루어 진다.
            if (client.storeFile(file.replace(root, ""), fi)) {
              System.out.println("Upload - " + file);
            }
          }
        }
        
        ///************다운로드***************
        // 리스트 초기화
        files.clear();
        directories.clear();
        // Ftp로부터 다운받아서 저장할 경로 설정
        root = "d:\\ftptest\\download";
        // FTP에서 파일 리스트와 디렉토리 정보를 취득한다.
        if (getFileList(client, File.separator, files, directories)) {
          // 디렉토리 구조대로 로컬 디렉토리 생성
          for (String directory : directories) {
            File file = new File(root + directory);
            file.mkdir();
          }
          for (String file : files) {
            // 파일의 OutputStream을 가져온다.
            try (FileOutputStream fo = new FileOutputStream(root + File.separator + file)) {
              // FTPClient의 retrieveFile함수로 보내면 다운로드가 이루어 진다.
              if (client.retrieveFile(file, fo)) {
                System.out.println("Download - " + file);
              }
            }
          }
        } else {
          // 리스트 취득 실패시 프로그램을 종료한다.
          System.out.println("File search Error!");
          return;
        }
        // ftp를 로그아웃한다.
        client.logout();
      }
    } catch (Throwable e) {
      e.printStackTrace();
    } finally {
      // ftp 커넥션이 연결되어 있으면 종료한다.
      try {
        if (client.isConnected()) {
          client.disconnect();
        }
      } catch (Throwable e) {
        e.printStackTrace();
      }
    }
  }
  // 로컬의 파일 리스트와 디렉토리 정보를 취득하는 함수.
  private static void getUploadList(String root, List<String> files, List<String> directories) {
    File upload = new File(root);
    // root로 받은 경로의 파일 리스트를 받아 온다.
    for (File file : upload.listFiles()) {
      // 리스트의 객체가 파일이면
      if (file.isFile()) {
        // files 리스트에 경로를 추가한다.
        files.add(file.getAbsolutePath());
      } else {
        // 디렉토리리면 함수의 재귀적 방식으로 하위 탐색을 시작한다.
        getUploadList(file.getAbsolutePath(), files, directories);
        // directories 리스트에 디렉토리 경로를 추가한다.
        directories.add(file.getAbsolutePath().replace(root, ""));
      }
    }
  }
  // FTP의 파일 리스트와 디렉토리 정보를 취득하는 함수.
  private static boolean getFileList(FTPClient client, String cw, List<String> files, List<String> directories)
      throws IOException {
    // FTP의 디렉토리 커서를 이동한다.
    if (client.changeWorkingDirectory(cw)) {
      // 해당 디렉토리의 파일 리스트를 취득한다.
      for (FTPFile file : client.listFiles()) {
        // 리스트의 객체가 파일이면
        if (file.isFile()) {
          // files 리스트에 경로를 추가한다.
          files.add(cw + file.getName());
        } else {
          // 디렉토리리면 함수의 재귀적 방식으로 하위 탐색을 시작한다.
          if (!getFileList(client, cw + file.getName() + File.separator, files, directories)) {
            return false;
          } else {
            // directories 리스트에 디렉토리 경로를 추가한다.
            directories.add(cw + file.getName() + File.separator);
          }
        }
      }
      // 이건 FTP의 디렉토리 커서를 상위로 이동하는 함수입니다.(여기서는 사용하지 않았으나 자주 사용하는 함수입니다.)
      // client.changeToParentDirectory();
      // FTP의 디렉토리 커서를 이동한다.
      return client.changeWorkingDirectory(File.separator);
    }
    // 커서 이동에 실패하면 false를 리턴한다.
    return false;
  }
}

위의 소스는 Program의 main함수에 FTP 환경의 파일과 디렉토리를 모두 삭제하고 ftptest/upload의 디렉토리의 파일과 하위 디렉토리, 파일을 전부 업로드를 하고, ftptest/download로 전부 다운로드하는 소스의 예제입니다.

위처럼 있는 파일들을 ftp://localhost(ftptest/ftp)로 업로드가 될 것입니다.

그리고 다시 위의 파일을 ftptest/download로 다운로드하는 것입니다.

프로그램을 실행하겠습니다.

다시 ftp://localhost(ftptest/ftp)로 이동하여 확인합니다.

ftp 서버에 제대로 업로드가 되었습니다.

다운로드도 잘 되었습니다.


워낙 간단한 소스라서 하나의 파일에 모든 예제를 담아 보았습니다.

많이 사용하는 것으로는 listFiles 함수의 FTP 파일 리스트 취득과 changeWorkingDirectory함수의 디렉토리 커서 이동입니다.

커서 이동에 대해 따로 현재 위치를 알려주는 함수가 없습니다. FTPClient가 아닌 내부에서 커서 이동을 잘 관리해야 합니다.


위에서는 업로드할 때 storeFile을 사용했는데 이건 기존에 파일이 있으면 새로 덮어 씌우기합니다. 그러나 FTP에서는 기존 파일 뒤에 내용을 추가하는 함수도 있는데 appendFile 함수입니다.

appendFile의 경우는 파일이 없으면 추가하고 기존에 파일이 존재하면 그 뒤에 내용을 추가 작성하는 함수입니다.


참조 - https://commons.apache.org/proper/commons-net/apidocs/org/apache/commons/net/ftp/FTPClient.html


여기까지 Java에서 FTP에 접속하여 파일 다운로드, 업로드하는 방법(FTPClient)에 대한 설명이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.