[java] NIO 소켓 프로그래밍 개념과 활용

소켓 프로그래밍이란?

소켓 프로그래밍(Socket Programming)은 네트워크 프로그래밍에서 가장 중요한 개념 중 하나입니다. 소켓은 컴퓨터 간에 데이터를 주고받는 데 사용되는 양방향 통신 채널입니다. 네트워크 응용 프로그램에서 소켓을 이용하여 데이터를 전송하고 받을 수 있습니다.

NIO(Nondominated Input/Output)란?

NIO는 Java에서 제공하는 비동기 입출력 기능을 의미합니다. 기존의 IO 기능은 입출력 작업 시에 블로킹이 발생하여 다른 작업을 수행할 수 없었습니다. 하지만 NIO는 비동기 입출력을 지원하여 입출력 작업이 완료되지 않은 상태에서도 다른 작업을 수행할 수 있습니다. 이러한 특징은 네트워크 프로그래밍에서 매우 유용하게 사용됩니다.

NIO 소켓 프로그래밍 활용

NIO 소켓 프로그래밍은 다수의 클라이언트와 서버 간 양방향 통신을 지원하는데 유용합니다. NIO를 이용하여 다수의 클라이언트 요청을 효율적으로 처리하고, 대규모 트래픽을 처리하는 시스템을 구현할 수 있습니다.

다음은 NIO 소켓 프로그래밍의 예제 코드입니다.

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

public class NIOServer {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private ByteBuffer buffer = ByteBuffer.allocate(1024);
    private List<SocketChannel> clients = new ArrayList<>();
    
    public NIOServer(int port) {
        try {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("NIO Server started on port " + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        try {
            while (true) {
                int ready = selector.select();
                if (ready == 0) {
                    continue;
                }
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectedKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if (!key.isValid()) {
                        continue;
                    }
                    if (key.isAcceptable()) {
                        accept(key);
                    } else if (key.isReadable()) {
                        read(key);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void accept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel channel = serverChannel.accept();
        channel.configureBlocking(false);
        channel.register(selector, SelectionKey.OP_READ);
        clients.add(channel);
        System.out.println("Connection accepted: " + channel.getRemoteAddress());
    }

    private void read(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        buffer.clear();
        int bytesRead = channel.read(buffer);
        if (bytesRead == -1) {
            disconnect(channel);
            return;
        }
        buffer.flip();
        byte[] bytes = new byte[bytesRead];
        buffer.get(bytes);
        String message = new String(bytes);
        System.out.println("Received message: " + message);
        broadcast(channel, message);
    }

    private void disconnect(SocketChannel channel) throws IOException {
        channel.close();
        clients.remove(channel);
        System.out.println("Connection closed: " + channel.getRemoteAddress());
    }

    private void broadcast(SocketChannel sender, String message) throws IOException {
        for (SocketChannel channel : clients) {
            if (channel != sender) {
                ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
                channel.write(buffer);
                buffer.clear();
            }
        }
    }

    public void stop() {
        try {
            selector.close();
            serverSocketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        NIOServer server = new NIOServer(8080);
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(() -> server.start());
        try {
            TimeUnit.SECONDS.sleep(60);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        server.stop();
    }
}

위의 예제 코드는 NIO를 활용하여 간단한 다중 클라이언트 채팅 서버를 구현한 코드입니다. 다수의 클라이언트가 서버에 연결되면서 서로 메시지를 주고받을 수 있습니다.

결론

NIO 소켓 프로그래밍은 네트워크 프로그래밍에서 매우 유용하게 사용됩니다. 비동기 입출력을 이용하여 다수의 클라이언트와의 효율적인 통신을 구현할 수 있습니다. Java의 NIO 기능을 활용해 다양한 소켓 프로그래밍을 개발해 보세요.

참고 자료: