IT 강좌(IT Lectures)/Java

9강. 파일 I/O 및 네트워킹

소울입니다 2024. 6. 26. 08:30
728x90
반응형

챕터 9: 파일 I/O 및 네트워킹


Java의 파일 I/O(Input/Output)와 네트워킹 기능은 데이터의 읽기, 쓰기 및 네트워크 통신을 처리하는 강력한 도구입니다. 이 챕터에서는 파일 I/O와 네트워킹의 기본 개념, 주요 기능, 그리고 이를 활용한 예제 코드를 자세히 설명합니다.

9.1 파일 I/O

Java는 파일 I/O를 처리하기 위해 java.io와 java.nio 패키지를 제공합니다. 이 패키지들은 파일 읽기, 쓰기, 복사, 삭제 등을 수행하는 다양한 클래스를 포함하고 있습니다.

9.1.1 파일 읽기

파일을 읽는 방법에는 여러 가지가 있습니다. 가장 많이 사용하는 방법 중 하나는 BufferedReader를 사용하는 것입니다. 파일을 읽는 데 있어서 효율성과 편리성을 제공하는 것이 목적입니다.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReadExample {
    public static void main(String[] args) {
        // try-with-resources를 사용하여 BufferedReader와 FileReader를 자동으로 닫음
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            // 파일을 한 줄씩 읽어서 출력
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • BufferedReader와 FileReader: BufferedReader는 입력을 버퍼링하여 효율적으로 읽을 수 있게 도와줍니다. FileReader는 파일을 읽는 기본 클래스이며, BufferedReader와 함께 사용하면 더 빠르게 데이터를 읽을 수 있습니다. 이 방법은 큰 파일을 읽을 때 유용합니다. try-with-resources 구문을 사용하여 자원을 자동으로 해제합니다. 이 방식은 명시적으로 close() 메서드를 호출하지 않아도 되므로, 코드가 더 간결하고 안전해집니다.

추가 예제: 파일을 바이트 단위로 읽기

import java.io.FileInputStream;
import java.io.IOException;

public class FileReadByteExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("example.txt")) {
            int content;
            // 파일을 바이트 단위로 읽기
            while ((content = fis.read()) != -1) {
                System.out.print((char) content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 

설명:

  • FileInputStream: FileInputStream 클래스는 파일을 바이트 단위로 읽을 수 있게 해줍니다. 이는 바이너리 파일이나 이미지 파일을 읽을 때 유용합니다.

9.1.2 파일 쓰기

파일에 데이터를 쓰는 방법도 여러 가지가 있습니다. BufferedWriter를 사용하는 방법이 일반적입니다. 이는 파일에 데이터를 쓸 때 효율성을 높이고, 데이터를 버퍼링하여 성능을 향상시킵니다.

import java.io.FileInputStream;
import java.io.IOException;

public class FileReadByteExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("example.txt")) {
            int content;
            // 파일을 바이트 단위로 읽기
            while ((content = fis.read()) != -1) {
                System.out.print((char) content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 

설명:

  • BufferedWriter와 FileWriter: BufferedWriter는 출력을 버퍼링하여 효율적으로 쓸 수 있게 도와줍니다. FileWriter는 파일에 쓰는 기본 클래스이며, BufferedWriter와 함께 사용하면 더 빠르게 데이터를 쓸 수 있습니다. 이 방법은 파일에 빈번히 데이터를 쓸 때 유용합니다. newLine() 메서드는 시스템의 기본 줄 바꿈 문자를 사용하여 새 줄을 추가합니다. 이를 통해 플랫폼 독립적인 코드를 작성할 수 있습니다.

추가 예제: 파일을 바이트 단위로 쓰기

import java.io.FileOutputStream;
import java.io.IOException;

public class FileWriteByteExample {
    public static void main(String[] args) {
        String data = "Hello, Byte World!";
        try (FileOutputStream fos = new FileOutputStream("example.txt")) {
            // 문자열을 바이트로 변환하여 파일에 쓰기
            fos.write(data.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 

설명:

  • FileOutputStream: FileOutputStream 클래스는 파일에 바이트 단위로 데이터를 쓸 수 있게 해줍니다. 이는 바이너리 파일이나 이미지 파일을 쓸 때 유용합니다.

9.1.3 파일 복사

파일을 복사하는 방법에는 Files 클래스를 사용하는 것이 편리합니다. 이는 파일을 간편하게 복사할 수 있도록 도와줍니다.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileCopyExample {
    public static void main(String[] args) {
        // 소스 파일과 타겟 파일의 경로를 지정
        Path source = Paths.get("source.txt");
        Path target = Paths.get("target.txt");

        try {
            // 파일 복사
            Files.copy(source, target);
            System.out.println("File copied successfully!");
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • Files.copy: Files 클래스는 파일 복사, 이동, 삭제와 같은 작업을 수행하는 정적 메서드를 제공합니다. Files.copy 메서드는 소스 파일을 타겟 위치로 복사합니다. 이 메서드는 파일 복사를 간단하고 효율적으로 처리할 수 있게 해줍니다. 예외 처리를 통해 파일이 제대로 복사되지 않았을 때의 상황을 처리할 수 있습니다.

추가 예제: 파일 이동

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileMoveExample {
    public static void main(String[] args) {
        // 소스 파일과 타겟 파일의 경로를 지정
        Path source = Paths.get("source.txt");
        Path target = Paths.get("moved.txt");

        try {
            // 파일 이동
            Files.move(source, target);
            System.out.println("File moved successfully!");
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • Files.move: Files 클래스의 move 메서드는 파일을 지정된 위치로 이동시킵니다. 이는 파일의 물리적 위치를 변경하는 데 유용합니다.

9.1.4 파일 삭제

파일을 삭제하는 방법도 Files 클래스를 사용하는 것이 좋습니다. 이는 파일을 간편하게 삭제할 수 있도록 도와줍니다.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileDeleteExample {
    public static void main(String[] args) {
        // 삭제할 파일의 경로를 지정
        Path path = Paths.get("example.txt");

        try {
            // 파일 삭제
            Files.delete(path);
            System.out.println("File deleted successfully!");
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • Files.delete: Files 클래스의 delete 메서드는 지정된 파일을 삭제합니다. 파일이 존재하지 않으면 예외를 발생시켜 파일이 제대로 삭제되었는지 확인할 수 있습니다. 이 메서드는 파일 삭제 작업을 단순하고 명확하게 처리할 수 있게 해줍니다. 예외 처리를 통해 파일이 이미 삭제되었거나 존재하지 않을 때의 상황을 처리할 수 있습니다.

추가 예제: 파일이 존재하는지 확인하고 삭제

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileDeleteIfExistsExample {
    public static void main(String[] args) {
        // 삭제할 파일의 경로를 지정
        Path path = Paths.get("example.txt");

        try {
            // 파일이 존재하면 삭제
            if (Files.exists(path)) {
                Files.delete(path);
                System.out.println("File deleted successfully!");
            } else {
                System.out.println("File does not exist!");
            }
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • Files.exists: Files 클래스의 exists 메서드는 파일이 존재하는지 확인합니다. 파일이 존재하는 경우에만 삭제 작업을 수행할 수 있습니다.

9.2 네트워킹

Java의 네트워킹 기능은 java.net 패키지를 통해 제공됩니다. 이 패키지는 TCP와 UDP 통신을 처리하는 다양한 클래스를 포함하고 있습니다.

9.2.1 TCP 소켓 프로그래밍

TCP(Transmission Control Protocol)는 연결 지향 프로토콜로, 신뢰성 있는 통신을 제공합니다. 서버와 클라이언트 간의 연결을 통해 데이터를 주고받습니다.

  • TCP 서버 예제
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.IOException;

public class TCPServer {
    public static void main(String[] args) {
        // 서버 소켓을 12345 포트에서 열기
        try (ServerSocket serverSocket = new ServerSocket(12345)) {
            System.out.println("Server is listening on port 12345");
            while (true) {
                // 클라이언트 연결 대기
                Socket socket = serverSocket.accept();
                System.out.println("New client connected");

                // 클라이언트와 통신을 처리할 새 스레드 시작
                new ServerThread(socket).start();
            }
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}

class ServerThread extends Thread {
    private Socket socket;

    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        // try-with-resources를 사용하여 BufferedReader와 PrintWriter를 자동으로 닫음
        try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

            String message;
            // 클라이언트로부터 메시지를 읽고 에코 메시지 전송
            while ((message = in.readLine()) != null) {
                System.out.println("Received: " + message);
                out.println("Echo: " + message);
            }
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • ServerSocket: ServerSocket 클래스는 서버가 클라이언트의 연결을 수락할 수 있도록 합니다. 특정 포트에서 들어오는 연결 요청을 기다립니다. 클라이언트가 연결을 요청하면 새로운 소켓을 생성하여 통신을 처리합니다.
  • Socket: Socket 클래스는 클라이언트와 서버 간의 연결을 나타냅니다. 서버는 클라이언트의 요청을 수락하고, 소켓을 통해 클라이언트와 데이터를 주고받습니다.
  • BufferedReader와 PrintWriter: BufferedReader는 클라이언트로부터 데이터를 효율적으로 읽을 수 있게 해주고, PrintWriter는 클라이언트에게 데이터를 효율적으로 쓸 수 있게 해줍니다. 이는 클라이언트-서버 통신에서 문자열 데이터를 주고받을 때 유용합니다. 각 클라이언트 연결은 별도의 스레드에서 처리되어 다중 클라이언트를 동시에 처리할 수 있습니다.
  • TCP 클라이언트 예제
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.io.IOException;

public class TCPClient {
    public static void main(String[] args) {
        // 서버에 연결
        try (Socket socket = new Socket("localhost", 12345);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {

            String userInput;
            // 사용자 입력을 서버에 전송하고 에코 메시지를 출력
            while ((userInput = stdIn.readLine()) != null) {
                out.println(userInput);
                System.out.println("Echo from server: " + in.readLine());
            }
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • Socket: Socket 클래스는 클라이언트가 서버에 연결할 수 있도록 합니다. 클라이언트는 서버에 연결하고, 소켓을 통해 데이터를 주고받습니다. 이는 클라이언트-서버 통신에서 필수적인 연결 객체입니다.
  • BufferedReader와 PrintWriter: BufferedReader는 서버로부터 데이터를 효율적으로 읽을 수 있게 해주고, PrintWriter는 서버에게 데이터를 효율적으로 쓸 수 있게 해줍니다. 클라이언트는 사용자의 입력을 서버로 전송하고, 서버로부터의 응답을 출력합니다. 이는 텍스트 데이터를 쉽게 송수신할 수 있게 해줍니다.

추가 예제: 여러 클라이언트 지원

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TCPMultiClientServer {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(4);
        try (ServerSocket serverSocket = new ServerSocket(12345)) {
            System.out.println("Server is listening on port 12345");
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("New client connected");
                pool.execute(new ServerThread(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ServerThread extends Thread {
    private Socket socket;

    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

            String message;
            while ((message = in.readLine()) != null) {
                System.out.println("Received: " + message);
                out.println("Echo: " + message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 

설명:

  • ExecutorService: ExecutorService는 스레드 풀을 관리합니다. 이를 통해 서버는 여러 클라이언트 연결을 동시에 처리할 수 있습니다.
  • Executors.newFixedThreadPool: 고정된 크기의 스레드 풀을 생성합니다. 이 방법은 서버가 동시에 처리할 수 있는 클라이언트 연결 수를 제한합니다.

9.2.2 UDP 소켓 프로그래밍

UDP(User Datagram Protocol)는 비연결 지향 프로토콜로, 신뢰성은 떨어지지만 빠른 통신이 가능합니다. 데이터그램을 통해 데이터를 주고받습니다.

  • UDP 서버 예제
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.io.IOException;

public class UDPServer {
    public static void main(String[] args) {
        // UDP 소켓을 12345 포트에서 열기
        try (DatagramSocket socket = new DatagramSocket(12345)) {
            byte[] buffer = new byte[1024];

            while (true) {
                // 클라이언트로부터 데이터그램 수신
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet);
                String received = new String(packet.getData(), 0, packet.getLength());
                System.out.println("Received: " + received);

                // 에코 응답 전송
                String response = "Echo: " + received;
                byte[] responseBytes = response.getBytes();
                DatagramPacket responsePacket = new DatagramPacket(responseBytes, responseBytes.length, packet.getAddress(), packet.getPort());
                socket.send(responsePacket);
            }
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • DatagramSocket: DatagramSocket 클래스는 UDP 소켓을 나타냅니다. 서버는 지정된 포트에서 데이터그램을 수신하고, 데이터그램을 클라이언트로 전송합니다. 이는 비연결형 통신을 처리할 때 유용합니다.
  • DatagramPacket: DatagramPacket 클래스는 데이터그램을 나타냅니다. 데이터를 포함하는 패킷을 생성하고, 이를 통해 데이터를 주고받습니다. 이 클래스는 UDP 통신에서 데이터를 송수신하는 기본 단위입니다.
  • UDP 클라이언트 예제
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.io.IOException;

public class UDPClient {
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket()) {
            InetAddress address = InetAddress.getByName("localhost");
            byte[] buffer = new byte[1024];

            // 서버에 전송할 메시지 생성
            String message = "Hello, UDP Server!";
            byte[] messageBytes = message.getBytes();
            DatagramPacket packet = new DatagramPacket(messageBytes, messageBytes.length, address, 12345);
            socket.send(packet);

            // 서버로부터 응답 수신
            DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println("Response from server: " + response);
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • DatagramSocket: DatagramSocket 클래스는 UDP 소켓을 나타냅니다. 클라이언트는 서버에 데이터그램을 전송하고, 데이터그램을 통해 데이터를 수신합니다. 이는 비연결형 통신을 처리할 때 유용합니다.
  • DatagramPacket: DatagramPacket 클래스는 데이터그램을 나타냅니다. 클라이언트는 메시지를 포함하는 패킷을 생성하고, 이를 서버에 전송합니다. 서버로부터 응답을 수신하여 이를 출력합니다. 이 클래스는 UDP 통신에서 데이터를 송수신하는 기본 단위입니다.

추가 예제: 비동기 UDP 통신

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.concurrent.CompletableFuture;
import java.io.IOException;

public class AsyncUDPClient {
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket()) {
            InetAddress address = InetAddress.getByName("localhost");
            byte[] buffer = new byte[1024];

            // 서버에 전송할 메시지 생성
            String message = "Hello, UDP Server!";
            byte[] messageBytes = message.getBytes();
            DatagramPacket packet = new DatagramPacket(messageBytes, messageBytes.length, address, 12345);
            socket.send(packet);

            // 비동기로 응답 수신
            CompletableFuture.runAsync(() -> {
                try {
                    DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length);
                    socket.receive(responsePacket);
                    String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
                    System.out.println("Response from server: " + response);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).join();  // 응답을 기다림
        } catch (IOException e) {
            // IOException이 발생하면 스택 트레이스를 출력
            e.printStackTrace();
        }
    }
}
 

설명:

  • CompletableFuture.runAsync: CompletableFuture를 사용하여 비동기 작업을 수행합니다. 이를 통해 클라이언트는 서버로부터의 응답을 비동기적으로 수신할 수 있습니다.
  • join: 비동기 작업이 완료될 때까지 기다립니다. 이를 통해 비동기 작업의 완료를 동기적으로 처리할 수 있습니다.

이로써 Java의 파일 I/O 및 네트워킹에 대해 자세히 설명하고, 각 개념을 코드와 예시를 통해 설명했습니다. 다음 챕터에서는 Java를 이용한 기초 프로젝트 구성에 대해 다루겠습니다.

반응형

'IT 강좌(IT Lectures) > Java' 카테고리의 다른 글

11강. Java 성능 최적화  (0) 2024.06.28
10강. 기초 프로젝트  (0) 2024.06.27
8강. 스트림과 컬렉션 프레임워크  (0) 2024.06.25
7강. 동시성 프로그래밍  (0) 2024.06.24
6강. 고급 객체 지향 기법  (0) 2024.06.23