[PHP] FTP를 접속하여 파일을 업로드, 다운로드, FTP 초기화하는 방법


Development note/PHP  2020. 2. 20. 09:00

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


이 글은 PHP에서 FTP를 접속하여 파일을 업로드, 다운로드, FTP 초기화하는 방법에 대한 글입니다.


웹 서비스에서 FTP 프로토콜을 사용할 일이 있을까 하지만, 업로드가 많은 웹 서비스면 서버 내부에 파일을 두기에는 어려운 점이 많이 있습니다.

먼저, 서버 하드 디스크 용량은 무제한이 아니고 용량의 한도가 있습니다. 그러나 많은 업로드가 예상될 때, 서버는 웹 통신의 트래픽 관리 이외에 파일 관리를 해야하는 문제점이 생깁니다.

그래서 서버의 용도를 분산시키게 되는데 삼바(samba)로 mount해서 사용하는 것도 하나의 방법입니다만, 사양에 따라 같은 네트워크 내부가 아닌 경우에는 삼바(samba) 통신이 되지 않겠네요.

이럴 경우에는 Http에서 FTP로 접속하여 외부 서버에 접속해서 파일을 업로드 및 다운로드를 하는 것도 좋습니다.

이런 이유로 WebServer에서 FTP를 많이 사용합니다만, 최근에는 Cloud 개념이 생겨서... 예전보다는 많이 사용하지 않네요.


FTP 서버 설치는 이전에 설명한 적이 있으므로 참조하시면 됩니다.

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

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


예전에 Python으로 FTP 접속을 구현했었는데, 참고하시면 FTP 프로토콜에 대해 이해하실 수 있습니다.

링크 - [Python] FTP에 접속하여 파일 다운로드, 업로드하는 방법(ftplib)

링크 - https://ko.wikipedia.org/wiki/FTP_명령어_목록


먼저 PHP에서 ftp를 사용하려면 PHP.ini를 설정해야 합니다.

「extension=ftp」부분의 주석을 해제하면 됩니다. (※참고로 저는 주석 해제가 아니고 값이 존재하지 않아서 직접 입력했습니다.)

그리고 PHP에서 ftp를 접속하여 삭제, 업로드, 다운로드를 구현하겠습니다.

<?php
// 리스트 정보(MM-DD-YY HH:MM <DIR> 디렉토리이름)에서 <DIR>이 포함되어 있는지 확인 함수
function is_directory($str) {
  // 0 이상이면 true, -1이면 false
  return stripos($str, '<DIR>');
}
// 리스트 정보(MM-DD-YY HH:MM <DIR> 파일이름)에서 파일이름을 취득하는 함수
function get_name($str) {
  // 문자열 뒤에서 공백을 찾는다.
  $pos = strripos($str, ' ');
  // 공백에서부터 끝까지 문자열을 자른다.
  return substr($str, $pos + 1);
}
// FTP 서버의 모든 디렉토리, 파일을 삭제한다.
function delete_all_ftp($ftp, $cwd = "") {
  // FTP서버의 리스트를 취득한다.(DETAIL)
  $list = ftp_rawlist($ftp, $cwd);
  // 리스트를 Iteration 방식으로 데이터를 받는다.
  foreach ($list as $val) {
    // 디렉토리인지 확인
    if(is_directory($val)) {
      // 디렉토리라면 재귀적 방법으로 하위 디렉토리의 파일을 삭제한다.
      delete_all_ftp($ftp, $cwd.get_name($val)."/");
    } else {
      // 파일이면 삭제한다.
      ftp_delete($ftp, $cwd.get_name($val));
    }
  }
  // 디렉토리 삭제
  if ($cwd != "") {
    ftp_rmdir($ftp, $cwd);
  }
}
// 업로드하는 함수
function upload_ftp($ftp, $name, $path) {
  // 오늘 날짜 YYYYMMDD형식으로 디렉토리를 이동한다.
  if(!@ftp_chdir($ftp, date("Ymd"))) {
    // 이동이 안되면 폴더 생성한다.
    ftp_mkdir($ftp, date("Ymd"));
    // 다시 이동한다.
    ftp_chdir($ftp, date("Ymd"));
  }
  try {
    // 파일 커넥션을 만든다.
    $fp = fopen($path,"r");
    // 파일을 ftp 서버로 업로드 한다.
    ftp_fput($ftp, $name, $fp, FTP_BINARY);
  } finally {
    // 파일 커넥션 닫는다.
    fclose($fp);
  }
  // ftp 접속 디렉토리를 root로 이동한다.
  ftp_chdir($ftp, "/");
}
// ftp에서 파일 리스트 탐색
function search_ftp($ftp, $cwd = "", $ret = []) {
  // FTP서버의 리스트를 취득한다.(DETAIL)
  $list = ftp_rawlist($ftp, $cwd);
  // 리스트를 Iteration 방식으로 데이터를 받는다.
  foreach ($list as $val) {
    // 디렉토리인지 확인
    if(is_directory($val)) {
      // 디렉토리라면 재귀적 방법으로 하위 디렉토리의 파일을 탐색한다.
      $ret = search_ftp($ftp, $cwd.get_name($val)."/", $ret);
    } else {
      // 파일이라면 결과 리스트에 파일 명을 추가한다.
      array_push($ret, $cwd.get_name($val));
    }
  }
  // 결과 리스트 반환
  return $ret;
}
// ftp에서 파일를 다운로드
function download_ftp($ftp, $path) {
  // ftp에서 파일을 다운로드하여 임시로 파일을 생성하는 경로
  $temp = "D:\\ftptest\\temp\\".uniqid();
  try {
    // 파일 커넥션을 만든다.
    $fp = fopen($temp,"w");
    // ftp 서버로 부터 파일을 생성한다.
    ftp_fget($ftp, $fp, $path, FTP_BINARY, 0);
  } finally {
    // 파일 커넥션 닫는다.
    fclose($fp);
  }
  // 디렉토리 경로를 제외한 파일명을 변환
  $pos = strripos($path, '/');
  $name = substr($path, $pos + 1);
  // 응답 해더 재 설정
  header('Content-Description: File Transfer');
  // 다운로드 타입
  header('Content-Type: application/octet-stream');
  // 파일명 지정
  header('Content-Disposition: attachment; filename="'.basename($name).'"');
  header('Expires: 0');
  header('Cache-Control: must-revalidate');
  header('Pragma: public');
  // 파일 사이즈 설정
  header('Content-Length: ' . filesize($temp));
  // 바이너리 body에 출력
  readfile($temp);
  // 임시 파일 삭제
  unlink($temp);
  // 응답
  die();
}
// 화면 메시지 변수
$msg = "";
// select 박스에 파일 목록 리스트
$list = [];
try {
  // ftp에 접속한다.
  $ftp = ftp_connect("localhost");
  // 로그인을 한다.
  if (ftp_login($ftp, "FTPUser", "password")) {
    // Web 요청 method가 POST라면
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
      // FTP의 모든 파일 삭제 타입
      if($_POST["type"] === "all_delete") {
        // FTP의 모든 파일 삭제
        delete_all_ftp($ftp);
        // 완료 메시지 작성
        $msg = "All file was deleted.";
      // FTP 서버에 파일을 업로드 함.
      } else if($_POST["type"] === "upload") {
        // input type=file에 multiple를 추가하면 배열 형식으로 데이터가 온다.
        $count = count($_FILES["upload"]["name"]);
        // 배열의 개수 만큼 Iterate한다.
        for($i=0; $i<$count; $i++) {
          // 업로드를 호출한다. (파일 이름에 공백이 있으면 나중에 리스트 검색시 잘못된 데이터를 가져온다.
          // PHP는 업로드시 파일이 임시 폴더에 있다.
          upload_ftp($ftp, str_replace(' ','',$_FILES["upload"]["name"][$i]), $_FILES["upload"]["tmp_name"][$i]);
          // 업로드 성공 메시지 작성.
          $msg .= "The file was uploaded. - " . $_FILES["upload"]["name"][$i] . "<br />";
        }
      // FTP 서버에서 파일을 다운로드 함.
      } else if($_POST["type"] === "download") {
        download_ftp($ftp, $_POST["download"]);
      }
    }
    // POST, GET 상관없이 select 박스에 FTP 서버의 파일 리스트를 표시한다.
    $list = search_ftp($ftp);
  } else {
    // 로그인 실패하면 메시지를 표시한다.
    $msg = "The login was failed.";
  }
} finally {
  ftp_close($ftp);
}
?>
<!DOCTYPE html>
<html>
  <head>
    <title>title</title>
    <!-- Jquery 링크 -->
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
  </head>
  <body>
    <!-- 결과 메시지 표시 -->
    <?=$msg?>
    <br />
    <!-- form이 submit이 발생하면 POST형시으로 요청한다. -->
    <form method="POST">
      <!-- 타입을 구분하기 위한 값 -->
      <input type="hidden" id="type" name="type">
      <!-- 파일 전체 삭제 버튼 -->
      <button onclick="$('form #type').val('all_delete');
                       $('form').attr('enctype','');
                       $('form').submit();">delete all</button>
      <br /><br />
      <!-- multiple 타입이여서 복수 선택이 가능한다.-->
      <input type="file" name="upload[]" multiple>
      <!-- 업로드 버튼 -->
      <button onclick="$('form #type').val('upload');
                       $('form').attr('enctype','multipart/form-data');
                       $('form').submit();">upload</button><br />
      <br /><br />
      <!-- select 박스에 ftp 서버의 파일 일람을 표시함->
      <select name="download">
        <?php foreach($list as $item) {?>
        <option value="<?=$item?>"><?=$item?></option>
        <?php }?>
        <?php if(count($list) < 1) { ?>
        <option value="">No Item</option>
        <?php }?>
      </select>
      <!-- 다운로드 버튼->
      <button onclick="$('form #type').val('download');
                       $('form').attr('enctype','');
                       $('form').submit();">download</button>
    </form>
  </body>
</html>

위 소스를 실행하면 아래와 같은 화면이 나옵니다.

일단 ftp서버에 아무것도 없어서 아래 select box에 No Item 리스트가 나옵니다.

그럼 이미지를 두개 선택해서 업로드하겠습니다.

이미지를 두개 선택하고 업로드 버튼을 누릅니다.

업로드가 완료되었습니다.

FTP서버의 base 폴더를 가보니 파일이 제대로 업로드가 된 것을 확인할 수 있습니다.

선택하고 다운로드 버튼을 누릅니다.

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

이번에는 FTP 서버의 파일과 디렉토리를 전부 삭제합니다.

삭제되었다는 메시지가 나옵니다.

디렉토리와 파일이 전부 삭제되었습니다.


Web Document에서 파일을 웹 서버로 업로드 하면 웹 서버는 FTP로 파일을 업로드합니다. 또 반대로 다운로드 요청을 하면 웹서버에서 FTP로 접속하여 파일을 다운로드하고 브라우저로 보내줍니다.

참조 - https://www.php.net/manual/en/function.ftp-connect.php


여기까지 PHP에서 FTP를 접속하여 파일을 업로드, 다운로드, FTP 초기화하는 방법에 대한 설명이었습니다.


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