최원종의 개발 블로그

(소켓 - 7) 소켓을 활용한 파일 전송 (1:1) 본문

Java/JAVA 유용한 클래스

(소켓 - 7) 소켓을 활용한 파일 전송 (1:1)

chl6698 2026. 3. 27. 16:47

채팅에서 파일 전송 하기

//지금까지 한 것
소켓-2~6 : [프로그램] ──문자열──► [프로그램]

//진행할 내용
소켓-7   : [프로그램] ──파일──► [프로그램]

 

파일 전송 순서

1. 파일 이름  (서버가 어떤 이름으로 저장할지 알아야 함)
2. 파일 내용  (실제 데이터)

로컬 파일 복사 방법

// 로컬 파일 복사
FileInputStream  fis = new FileInputStream("원본.zip");
FileOutputStream fos = new FileOutputStream("복사본.zip");

byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
    fos.write(buffer, 0, bytesRead);
}

 

파일 전송 방법

// 소켓-7 - 네트워크로 파일 전송
FileInputStream fis = new FileInputStream("보낼파일.zip");
OutputStream    out = socket.getOutputStream(); // 파일 대신 네트워크로

// 4096 크기는 내가 임의로 정한 크기 입니다. 4096 = 2의 12승 = 4KB
// 하지만 사전 기반 지식으로 운영체제가 디스크에서 데이터를 읽을 때 보통 4KB 단위로 처리합니다.
// 버퍼 크기를 4KB 로 맞추면 운영체제 처리 단위와 딱 맞아서 효율적입니다.
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
    out.write(buffer, 0, bytesRead); // 파일에 쓰는 대신 네트워크로 전송
}
       파일 복사:  FileInputStream → FileOutputStream  (로컬 → 로컬)
소켓-7 파일 전송:  FileInputStream → socket.getOutputStream()  (로컬 → 네트워크)
                   socket.getInputStream() → FileOutputStream  (네트워크 → 로컬)

 


 

파일 이름을 먼저 보내는 이유 (프로토콜(약속))

//서버가 파일을 받기 전에 파일 이름을 알아야 저장할 수 있다.
//(나에게 파일을 보낼려면 약속을 먼저 지켜 파일 이름 보내고 다음에 파일데이터를 보내) 

클라이언트가 보내는 순서:

1단계: [파일 이름] - 고정 100바이트
       "employees.zip" + 빈 공간(나머지는 0)
       [e][m][p]...[p][0][0]...[0]
        ← 13바이트 이름 →← 87바이트 빈 공간 →

2단계: [파일 내용] - 실제 파일 데이터
       [바이트 1][바이트 2]...[파일 끝]

 

고정 100바이트 사용 이유

소켓은 바이트 스트림을 연속으로 전송합니다.
파일 이름과 파일 내용이 붙어서 오면
서버가 어디까지가 이름이고 어디서부터 내용인지 구분할 수 없습니다.

고정 100바이트로 정해두면:
  서버가 정확히 100바이트를 이름으로 읽고
  그 다음부터는 파일 내용으로 처리

이처럼 서버와 클라이언트가 데이터 형식을 미리 약속하는 것을 프로토콜이라고 합니다.

 

 


 

파일 서버측 코드

package server.ch05;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class SimpleFileServer {

    private static final int PORT = 5000;
    private static final String UPLOAD_DIR = "Uploads/";

    public static void main(String[] args) {
        //실행에 흐름 처리
        //먼저 저장 폴더 생성하는 법
        new File(UPLOAD_DIR).mkdir();// 저장 폴더 생성
        System.out.println("파일 서버 시작 - 포트 : " + PORT);

        try (ServerSocket serverSocket = new ServerSocket(PORT);
             Socket clientSocket = serverSocket.accept() // 블로킹 상태
        ) {
            System.out.println("클라이언트 연결 됨");
            handleClient(clientSocket);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }//end of main

    //파일 처리 기능 함수
    private static void handleClient(Socket socket) {
        try (
                InputStream in = socket.getInputStream();
                OutputStream out = socket.getOutputStream()


        ) {
            //1단계 : 파일 이름 수신 ( 고정 100바이트)
            byte[] nameBuffer = new byte[100];
            in.read(nameBuffer);//한 번에 100바이트 읽기
            //이 다음부터 아래에서 다시 in.read() 는 파일 내용을 읽음
            //[a][b][c][.][t][x][t][0][0][0]....
            String fileName = new String(nameBuffer).trim();
            //trim(): 뒤에 붙은 빈 공간(0바이트)제거
            System.out.println("수신할 파일 이름 : " + fileName);

            //2단계: 파일 내용 수신 후 서버측 컴퓨터 저장
            File file = new File(UPLOAD_DIR + fileName);
            try (FileOutputStream fos = new FileOutputStream(file)) {
                //클라이언트가 보낸 데이터를 읽어서 서버측 컴퓨터에 저장해야 함.
                byte[] buffer = new byte[4096];//크기 선언
                int bytesRead;//배열 담을 변수
                while ((bytesRead = in.read(buffer)) != -1) { //4kb만큼 담기
                    fos.write(buffer, 0, bytesRead);//읽은 만큼만 저장
                }
            }
            System.out.println("파일 저장 완료 : " + UPLOAD_DIR + fileName);
            //클라이언트 측에 완료 메세지 전송
            out.write(("파일 업로드 성공 : " + fileName).getBytes());
            out.flush();


        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}//end of class

 


 

-클라이언트 측 코드

package client.ch06;

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class SimpleFileClient {

    private static final int PORT = 5000;

    public static void main(String[] args) {
        System.out.println("파일 전송 클라이언트 시작");

        Scanner sc = new Scanner(System.in);
        System.out.println("전송할 파일 경로를 입력하세요(예 :C:\\_work_Java\\test.txt)");
        String filePath = sc.nextLine();

        File file = new File(filePath);
        if (!file.exists() || !file.isFile()) {
            System.out.println("파일이 존재하지 않습니다");
            return;
        }

        try (Socket socket = new Socket("localhost", PORT)) {
            OutputStream out = socket.getOutputStream();
            InputStream in = socket.getInputStream();

            //1단계: 파일 이름 전송 ( 고정 100바이트)
            //C:/test.txt  --> file.getName() --? test.txt 파싱했다
            String fileName = file.getName();
            byte[] nameBytes = fileName.getBytes();
            byte[] nameBuffer = new byte[100]; // 100바이트 선언

            //방어적 코드 - 파일 이름이 100바이트 이상 안되게처리
            if (nameBytes.length > 100) {
                System.out.println("파일 이름이 너무 깁니다.(최대 100자)");
                return; //코드가 더 안내려가게 반환
            }

            //파일 이름 전송
            for (int i = 0; i < nameBytes.length; i++) {
                nameBuffer[i] = nameBytes[i];
            }
            //서버측에서 딱 한 번 100바이트 통으로 읽기 위한 코드로 작성되어 있음
            out.write(nameBuffer);
            out.flush();

            //2단계 파일 내용 전송
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
                out.flush();
            }
            System.out.println("전송 완료 : " + fileName);

            //3단계 서버측 응답 수신
            byte[] responseBuffer = new byte[1024];
            int responseLength = in.read(responseBuffer);
            if (responseLength > 0) {
                System.out.println("서버 응답 : " + new String(responseBuffer, 0, responseLength));
            }


        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }//end of main
}//end of class

 


코드 전체 흐름

클라이언트                               서버
     |                                     |
new Socket(5000) ──────────────► accept()
     |                                     |
[파일 이름 100바이트] ──────────► in.read(nameBuffer) → "employees.zip"
     |                                     |
[파일 내용 4KB 씩] ──────────────► fos.write(buffer, 0, bytesRead) → 파일 저장
     |                                     |
     ◄─────────────────────────── "파일 업로드 성공" 응답

 


핵심 요약

파일 전송 = 파일 이름 먼저(100바이트) + 파일 내용 전송

프로토콜
  → 서버와 클라이언트가 데이터 형식을 미리 약속
  → 고정 100바이트 = 이름 끝을 정확히 알기 위한 약속

I/O 단원 연결
  → 18단원 FileInputStream + write(buffer, 0, bytesRead) 그대로 적용
  → FileOutputStream 자리에 socket.getOutputStream() 교체

trim()
  → 100바이트 배열 뒷부분의 빈 공간(0) 제거