JAVA

자바 네트워크 입출력(Network IO)

EunaSon 2025. 3. 7. 08:25

1. 네트워크 입출력이란?

네트워크 입출력(Network IO)은 네트워크를 통해 데이터를 송수신하는 작업을 의미한다. 파일 입출력과 달리, 네트워크 환경에서는 클라이언트와 서버 간의 데이터 전송이 필요하며, 다양한 예외 상황이 발생할 가능성이 높다. 자바는 이러한 네트워크 프로그래밍을 쉽게 구현할 수 있도록 다양한 API를 제공한다. 네트워크 입출력 방식은 크게 블로킹(Blocking)과 논블로킹(Non-blocking) 방식으로 구분되며, 사용 목적과 성능 요구사항에 따라 적절한 방식을 선택해야 한다.

2. 자바 네트워크 프로그래밍 개요

자바에서는 네트워크 프로그래밍을 위해 java.net 패키지와 java.nio.channels 패키지를 제공한다. java.net 패키지는 직관적인 블로킹 방식의 API를 제공하며, java.nio.channels 패키지는 고성능 네트워크 프로그래밍을 위한 논블로킹 API를 제공한다.

2.1 기존 네트워크 입출력 (java.net 패키지)

java.net 패키지는 전통적인 네트워크 프로그래밍을 위한 클래스를 포함하며, 간단한 서버-클라이언트 통신을 구현할 수 있다.

2.1.1 SocketServerSocket

Socket 클래스는 클라이언트가 서버와 연결을 맺을 때 사용되며, 서버와의 데이터 송수신을 담당한다.

import java.io.*;
import java.net.*;

public class SimpleClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 12345);
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
            
            out.println("Hello Server"); // 서버로 메시지 전송
            String response = in.readLine(); // 서버 응답 수신
            System.out.println("Server response: " + response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ServerSocket 클래스는 서버에서 클라이언트의 연결을 수락하고, Socket 객체를 반환하여 데이터를 송수신할 수 있도록 한다.

import java.io.*;
import java.net.*;

public class SimpleServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(12345)) {
            System.out.println("Server is listening on port 12345");
            
            while (true) {
                Socket socket = serverSocket.accept(); // 클라이언트 연결 수락
                
                try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                     PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
                    
                    String message = in.readLine(); // 클라이언트로부터 메시지 수신
                    System.out.println("Received: " + message);
                    out.println("Hello Client"); // 클라이언트에 응답 전송
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

2.2 NIO 기반 네트워크 입출력 (java.nio.channels 패키지)

기존 java.net 패키지는 블로킹 I/O 방식으로 동작하며, 다수의 클라이언트를 처리하는 데 한계가 있다. 이를 해결하기 위해 java.nio.channels 패키지를 활용한 논블로킹 네트워크 입출력이 가능하다.

2.2.1 SocketChannelServerSocketChannel

SocketChannelServerSocketChannel은 논블로킹 방식으로 소켓을 다룰 수 있는 API를 제공한다.

SocketChannel (클라이언트 소켓 채널)

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NioClient {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("localhost", 12345));
            
            while (!socketChannel.finishConnect()) {
                // 연결 완료까지 대기
            }
            
            ByteBuffer buffer = ByteBuffer.allocate(256);
            buffer.put("Hello Server".getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            
            buffer.clear();
            socketChannel.read(buffer);
            System.out.println("Server response: " + new String(buffer.array()).trim());
            
            socketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.2.2 Selector를 활용한 다중 클라이언트 처리

Selector는 하나의 스레드로 여러 소켓 채널을 관리할 수 있도록 해주는 핵심 요소이다. 이를 활용하면 대량의 클라이언트 요청을 효율적으로 처리할 수 있다.

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NioSelectorServer {
    public static void main(String[] args) {
        try {
            Selector selector = Selector.open();
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.bind(new InetSocketAddress(12345));
            serverChannel.configureBlocking(false);
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            
            System.out.println("NIO Server with Selector started on port 12345");

            while (true) {
                selector.select(); 
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    keyIterator.remove(); 

                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = server.accept();
                        clientChannel.configureBlocking(false);
                        clientChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("Client connected");
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}