소켓을 이용해서 원격지로 파일 전송하는 애플리케이션 작성 중 깜빡 잘못된 생각으로 오류에 빠졌던 내용을 정리하고 개선된 소스를 작성해본다.
처음 생각한 스트림의 구조는 아래와 같다.
서버의 파일을 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();
}
}
}