자바(SE)

Socket을 통한 binary 파일 전송. 잠깐의 잘못된 생각과 정리

  • -
반응형

소켓을 이용해서 원격지로 파일 전송하는 애플리케이션 작성 중 깜빡 잘못된 생각으로 오류에 빠졌던 내용을 정리하고 개선된 소스를 작성해본다.

처음 생각한 스트림의 구조는 아래와 같다.

 

서버의 파일을 FileInputStream으로 읽어와서 소켓에 기반한 BufferedOutputStream으로 클라이언트로 출력하고 클라이언트는 역시 소켓에 기반한 BufferedInputStream으로 읽은 후 FileOutputStream으로 로컬 파일에 출력한다. 

 

이에 대한 서버와 클라이언트의 코드는 각각 아래와 같다.

public class ServerFile {
  public static void main(String[] args) {
    try (ServerSocket ss = new ServerSocket(6547)) {
      while (true) {
        try (Socket socket = ss.accept();
          BufferedOutputStream bout = new BufferedOutputStream(socket.getOutputStream());
          BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

          BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("c:/windows/explorer.exe")));) {
          byte[] buffer = new byte[1000];
          int readed = -1;
          while ((readed = bin.read(buffer)) > 0) {
            bout.write(buffer, 0, readed);
          }						
          bout.flush();
          //System.out.println(br.readLine());
        } catch (IOException e) {
          System.out.println("통신 오류");
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
public class ClientFile {
  public static void main(String[] args) {
    String serverIp = "localhost";
    try (
        Socket socket = new Socket(serverIp, 6547);
        BufferedInputStream bin = new BufferedInputStream(socket.getInputStream());
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("c:/Temp/" + System.currentTimeMillis() + ".exe")));
      ) {
      byte[] buffer = new byte[1000];
      int readed = -1;
      while ((readed = bin.read(buffer)) > 0) {
        bout.write(buffer, 0, readed);
      }
      bout.flush();
      bout.close();
      //bw.write("잘 받았습니다.");
      //bw.flush();
      System.out.println("파일 전송 완료!!");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

여기까지 하면 파일 전송은 아주 잘 진행된다.  

여기서 한 발짝 나가서 클라이언트가 파일을 받으면 잘 받았다는 메시지를 서버로 출력하고자 한다.  위 코드들의 주석 처리된 부분이다. 희안하게도 주석을 풀고 서버와 클라이언트를 실행시키면 클라이언트의 코드는 블럭되어버린다. 정확히는 while 문장을 빠져나오지 못한다.

 

이유에 대해 한참을 고민했는데 원인은 단순했다. BufferedInputStream의 read를 중지 시키기 위해 -1이 리턴되는 상황을 기준으로 작성했는데 -1은 더이상 스트림으로 부터 읽을 데이터가 없을 때를 의미한다. 하지만 일반 파일에서 데이터를 읽으면 언제 끝이라는 것을 알 수 있지만 네트워크 너머의 소켓에서는 -1값이 넘어오지 않는다. 따라서 코드는 while에서 계속 데이터를 읽기 위해 대기중이다. 

 

주석했을 때 코드가  동작했던 이유는 클라이언트가 종료하면서 소켓이 닫히면서 얻어진 부수적인 효과였다.

 

새로운 시도로 먼저 서버가 클라이언트에게 전달할 총 데이터가 얼마나 큰지 정보를 전달하고 클라이언트에서는 

그만큼만 정보를 읽도록 처리한다. 전송할 데이터 크기(long), 전송할 데이터(byte [])를 모두 처리하기 위해서 DataInput/OutputStream을 사용한다.

 

public class ServerFile {
  public static void main(String[] args) {
    try (ServerSocket ss = new ServerSocket(6547)) {
      File file = new File("c:/windows/explorer.exe");
        while (true) {
          try (Socket socket = ss.accept();
            DataOutputStream dout =  new DataOutputStream( new BufferedOutputStream(socket.getOutputStream()));
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedInputStream bin = new BufferedInputStream(new FileInputStream(file));) 
          {
            System.out.printf("client connected: %s%n", socket.getInetAddress());
            long fileSize = file.length();
            dout.writeLong(fileSize);								// 전송할 데이터 크기를 미리 전달한다.
            byte[] b = new byte[10000];
            int readed = -1;
            while ((readed = bin.read(b)) > 0) {
              dout.write(b, 0, readed);
            }
            dout.flush();
            System.out.println("response message: "+br.readLine());	// 클라이언트 메시지 출력
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
}
public class ClientFile {
  public static void main(String[] args) {
    String serverIp = "localhost";
    try (
      Socket socket = new Socket(serverIp, 6547);
      DataInputStream bin = new DataInputStream(socket.getInputStream());
      BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
      BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("c:/Temp/" + System.currentTimeMillis() + ".exe")));
    ) {
      long size = bin.readLong();		// 전송받을 데이터의 총 크기
      System.out.println(size);
      int readed = 0;
      byte [] b = new byte[10000];
      while(true) {
        readed = bin.read(b);
        bout.write(b, 0, readed);
        size-=readed;
		if(size==0) {
          break;
        }
      }
      System.out.println("파일 전송 완료!!");
      bw.write("파일 다 받았어요\n");
      bw.flush();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

이제 읽어야할 데이터까지만 읽으면 다음 동작을 별 탈없이 진행할 수 있다.

반응형

'자바(SE)' 카테고리의 다른 글

Gson과 Json을 이용한 타입 파라미터 처리  (0) 2019.08.07
JSON 문자열 처리  (0) 2019.08.07
Thread의 join 사용 예  (0) 2019.08.03
instanceof Class vs instanceof Interface  (0) 2019.08.01
클래스간의 관계  (0) 2019.07.23
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.