이번에 이야기할 내용은 이전 포스팅 했던 Apache FTPClient을 이용하여 개발을 할 때 있었던 또 다른 이슈 내용과 경험이다.

FTP 연결을 이용하여 이미지를 보여주던 화면에서 사진이 자꾸 짤려서 보이는 것이었다. 파일을 가져오기 위한 코드 플로우는 다음과 같았다.

 

-> FTPClient 라이브러리의 메서드를 이용
-> InputStream 타입을 리턴
-> FTP 받은 이후 FTP 연결을 disconnect
-> InputStream 에서 byte[] 읽기
-> response 

 

과거에 Stream 에 대하여 공부할 때 Stream은 데이터의 흐름을 처리해주는 타입이라는 내용을 공부했었다. 위의 플로우로 코드를 짜두고 사내 FTP 서버에서 테스트시 아무 문제 없이 이미지가 잘 불러와졌다.
해당 테스트를 하면서 나는 이렇게 이해하였다. InputStream 타입은 데이터의 흐름을 처리해주는 객체다 보니 disconenct 되었어도 해당 타입 내부에 데이터 정보를 가지고 있다라는 생각이었다.

하지만 실제 운영서버 FTP 서버에서는 테스트에서와 다르게 사진이 잘라져서 나왔던 것이었다. 결국 Stream 이란 타입은 단지 흐름을 위한 통로 역할만 해주는 것이 맞았던 것이다.
코드 흐름을 아래와 같은 플로우로 변경하자 사진이 짤리던 현상은 해결되었다.

 

-> FTPClient 라이브러리의 메서드를 이용
-> InputStream 타입을 리턴
-> InputStream 에서 byte[] 읽기
-> FTP 받은 이후 FTP 연결을 disconnect
-> response 

 

 

해당 문제를 해결하면서 운영서버에서 사용하던 FTP 서버는 HDD라서 느릴수 있다는 이야기를 들었고, byte[]를 읽은 후 disconnect 하도록 하였더니 문제가 없이 잘 불러와졌다.
이러한 사실과 관련한 내용들을 찾아보면서 당시의 문제를 예상해보았다.
수정전 코드에서 흐름에서 disconnect 하는 메서드이후 byte를 읽었지만, I/O 쓰레드는 별개의 쓰레드였고, disconnect 실행 후 I/O 쓰레드가 연결이 끊기기전에 byte를 읽는 코드가 실행되어 연결이 끊어지기 전까지 데이터를 받았을 것이다.
사내 FTP 서버의 경우 운영서버에서 사용하던 FTP 서버보다 속도가 빨랐기에 연결이 끊기기전에 이미지 데이터를 모두 보내줄 수 있었지만 운영서버에서 사용하던 FTP 서버는 그러지 않았을 것이다. 따라서 수정한 코드는 모든 데이터를 확실하게 받고 disconnect 하게 되었기에 문제가 없었을 것이다.

Stream을 주고 받는 데이터 그 자체라고 착각하던 나였기에 해당 경험은 잘못 알고있던 부분에 대해서 제대로 이해하게 해주는 좋은 경험이 된 것 같다. 또한 쓰레드에 대한 내용들을 찾아보면서 실제 데이터의 흐름에 대해 더 생각해 볼 수 있는 기회가 되었다.

프로젝트 진행중에 NAS서버와 FTP 로 연결하여 이미지 파일을 저장, 보여주는 화면을 개발했을때 생겼던 문제이다.

FTP 서버 연결을 위해 구글 검색 후 Apache Commons Net( FTPClient (Apache Commons Net 3.10.0 API) 라이브러리를 이용하였다.

 

해당 문서에 들어가면 다음과 같은 기본 Example 코드를 제공해준다.

    FTPClient ftp = new FTPClient();
    FTPClientConfig config = new FTPClientConfig();
    config.setXXX(YYY); // change required options
    // for example config.setServerTimeZoneId("Pacific/Pitcairn")
    ftp.configure(config );
    boolean error = false;
    try {
      int reply;
      String server = "ftp.example.com";
      ftp.connect(server);
      System.out.println("Connected to " + server + ".");
      System.out.print(ftp.getReplyString());

      // After connection attempt, you should check the reply code to verify
      // success.
      reply = ftp.getReplyCode();

      if (!FTPReply.isPositiveCompletion(reply)) {
        ftp.disconnect();
        System.err.println("FTP server refused connection.");
        System.exit(1);
      }
      ... // transfer files
      ftp.logout(); 
    } catch (IOException e) {
      error = true;
      e.printStackTrace();
    } finally {
      if (ftp.isConnected()) {
        try {
          ftp.disconnect();
        } catch (IOException ioe) {
          // do nothing
        }
      }
      System.exit(error ? 1 : 0);
    }

 

해당 코드와 구글에 다른 개발자들이 남긴 흔적들을 템플릿-메소드 패턴으로 개발을 하였다.

파일을 가져오는 메서드를 반복하여 여러 파일을 불러오려고 시도하였으나 disconnect를 하지 않은상황임에도 FTPConnectionClosedException 가 발생하였기에 한 번에 하나의 파일정보만 가져올수 있는것으로 판단하였다.

따라서 여러 이미지를 불러오기 위해 이미지 태그를 사용하여 이미지별 Get 요청을 받아 이미지들을 Response 하다보니 요청하는 형태로 하였다.

 

코드를 완성한 후 테스트를 하다보니 한 번의 FTP 요청에 1~2초 정도 걸리는 상황을 발견하였다.

몇몇 화면에서는 이미지를 다수 보여줘야하는 상황이었으며, 이미지가 전부 로딩되는데 상당한 시간이 소모되었다. 

 

개발 후 테스트할 초기에는 단건씩 연결해야하는 상황과 FTP 서버 자체의 속도 문제인가라는 생각에 어쩔수 없다라고 생각했다. 하지만 뭔가 해결 방법을 찾고 싶어 확인하는 중에 신기한 현상(?)을 발견하였다. 난 정확히 얼마나 걸리나 파일을 가져오는 메서드 부분에 시간 로그를 찍어보았는데 빠르게 되는것이었다. 

 

어...째서? 그렇다면 왜 어디서 느리지 ? 라는 궁금증과 함께 코드 사이사이 시간로그를 모두 찍어보며 테스트한 결과 위 Example 코드에서 logout 메서드를 실행하는 곳에서 상당한 지연시간이 있음을 확인하였다.

      ... // transfer files
      
      
      ftp.logout();  <--- 바로 이곳!
      
      
    } catch (IOException e) {

 

 

아니 어째서 로그아웃이...

이후 해당 로그아웃 함수를 주석처리 후 테스트를 실행한 결과 상당한 속도의 향상이 있었다. 

로그아웃을 하지 않으면 문제가 없나 테스트하기 위해 수십장의 이미지를 불러오는 화면을 만들고 새로고침을 통하여 반복 테스트 해본결과 별다른 문제는 발생하지 않았다. 아마도 finally 코드 부분에서 결국 disconnect 하기때문에 문제가 발생하지 않는것으로 예상된다. Document 에서도 해당 메서드(logout)에 대해 단순히 QUIT command를 전송한다고만 되어있으며 IOException이 발생할수 있다 정도로만 설명이 적혀있었다.

public boolean logout() throws IOException

Logout of the FTP server by sending the QUIT command.
Returns:
True if successfully completed, false if not.
Throws:
FTPConnectionClosedException - If the FTP server prematurely closes the connection as a result of the client being idle or some other reason causing the server to send FTP reply code 421. This exception may be caught either as an IOException or independently as itself.
IOException - If an I/O error occurs while either sending a command to the server or receiving a reply from the server.

 

코드 라인라인마다 시간로그를 찍어보는 무식한(?) 방법이지만 속도향상을 할 수 있어서 만족했다.

 

 

혹시 FTPClient 를 사용하다 느려져서 검색하다 오신분을 위한 

요약: logout() 를 주석처리해보세요...

+ Recent posts