자바(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(); } } }

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

Contents

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

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