ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java NIO] Selector Wakeup 알아보기
    Java/NIO 2024. 6. 10. 06:32
    728x90
    반응형

    - 목차

     

    들어가며.

    Java NIO(Non-blocking IO) 는 효율적인 네트워크 프로그램을 작성할 수 있는 강력한 도구를 제공합니다.

    그중에서도 Selector 는 다중 채널에서 이벤트를 감지하여 효율적으로 비동기 작업을 처리할 수 있도록 도와줍니다.

    하지만 Selector를 사용할 때 wakeup() 메서드를 정확히 이해하고 사용하는 것이 중요합니다.

    이 글에서는 Selector.wakeup() 이 무엇인지, 왜 필요한지, 그리고 실제로 어떻게 동작하는지를 살펴보겠습니다.

     

    https://westlife0615.tistory.com/891

     

    [Java NIO] Selector register 이해하기

    - 목차 들어가며.Java NIO 의 Selector 는 비동기적으로 여러 채널(Channel)을 모니터링할 수 있는 강력한 도구입니다. 이 중에서도 register 메서드는 특정 채널을 Selector에 등록하고 관심 이벤트를 설정

    westlife0615.tistory.com

     

     

    Selector 의 기본적인 동작.

    Selector 는 하나 이상의 채널(Channel) 을 관리하고, 채널에서 발생한 특정 이벤트(읽기, 쓰기, 연결 등)를 감지하는 역할을 합니다.

    이를 통해 단일 스레드로 다중 채널을 효율적으로 처리할 수 있습니다.

     

    select.

    select() 함수는 기본적으로 무제한 대기하며, 이벤트가 발생할 때까지 차단됩니다.

    아래의 예시는 Selector 의 select 함수의 동작을 파악하기 위한 예시입니다.

    주목해야할 부분은 selector.select() 함수는 호출된 이후부터 Client 의 요청이 발생하기 전까지 무제한 대기하는 사실입니다.

    Socket 이 Binding 하는 8080 Port 로의 요청이 발생하기 이전까지 무제한 대기하게 되며,

    Client 의 네트워크 요청 이후에 다음의 코드로 이어지게 됩니다.

     

    package com.westlife.test;
    
    import java.net.InetSocketAddress;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    
    public class NIOTest {
    
        public static void main(String[] args) throws Exception {
            Selector selector = Selector.open();
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.bind(new InetSocketAddress(8080));
            serverChannel.configureBlocking(false);
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            selector.select();
    
            System.out.println("Client Request reached.");
    
        }
    }

     

    < Client HTTP Request >

    curl http://localhost:8080

     

    selectNow.

    selectNow 함수는 select 함수와 다르게 동작합니다.

    select 함수가 Channel 에 이벤트가 발생하기 이전까지 무제한 대기하는 성격인 반면에

    selectNow 를 즉시 요청된 이벤트의 유무를 반환하게 됩니다.

    selectNow 의 활용 예시는 아래와 같습니다.

    selectNow 는 즉시 발생 이벤트의 갯수를 반환하기에 Blocking 을 위한 코드를 명시해야합니다.

    그렇지 않으면 무의미한 CPU 를 계속 사용하게 됩니다.

     

    package com.westlife.test;
    
    import java.net.InetSocketAddress;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    
    public class NIOTest {
    
        public static void main(String[] args) throws Exception {
            Selector selector = Selector.open();
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.bind(new InetSocketAddress(8080));
            serverChannel.configureBlocking(false);
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            int eventCount = 0;
            while (eventCount < 1) {
                eventCount = selector.selectNow();
                Thread.sleep(1000);
            }
    
            System.out.println("Client Request reached.");
    
        }
    }

     

     

    Blocking Selector 의 문제.

    select 함수는 그 구조적으로 Selector 가 등록된 Thread 가 차단되는 문제가 있습니다.

    만약 Selector 에 새로운 Socket Channel 이나 Event 를 추가 등록하려고 한다면 이는 즉시 반영이 되지 않습니다.

     

    package com.westlife.test;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.channels.ClosedChannelException;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.util.Iterator;
    
    public class NIOTest {
    
        public static void main(String[] args) throws Exception {
            Selector selector = Selector.open();
            ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
            serverChannel1.bind(new InetSocketAddress(8080));
            serverChannel1.configureBlocking(false);
            serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
    
            ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
            serverChannel2.bind(new InetSocketAddress(8081));
            serverChannel2.configureBlocking(false);
    
    
            Thread thread1 = new Thread(() -> {
                while (true) {
                    try {
                        selector.select();
                        Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                        SelectionKey key = keys.next();
                        keys.remove();
    
                        System.out.println("Client Request reached.");
                    } catch (IOException e) {}
                }
            });
    
            Thread thread2 = new Thread(() -> {
                try {
                    Thread.sleep(1000);
                    serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
                } catch (Exception e) {}
            });
    
            thread1.start();
            thread2.start();
    
        }
    }

     

     

    wakeup()의 필요성.

    위 예시들에서 알아보았듯이 select() 에 의해서 Blocking 된 Selector 에 추가적인 Channel 이나 SelectionKey 를 등록시키기 위해서는 Blocking 상태가 해제되어야 합니다.

    Selector.wakeup()차단(Blocking) 중인 select() 호출을 강제로 깨우는 데 사용됩니다.

     

    아래의 예시는 2개의 Thread 에서 wakeup 의 필요성을 보여주는 예시입니다.

    1개의 Thread 는 1번 SocketChannel 의 Accept, Read 처리를 수행합니다.

    그리고 2번째 Thread 는 select() Blocking 중인 Selector 를 wakeup 으로 깨운 후, 새롭게 register 를 등록합니다.

     

    package com.westlife.test;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    public class NIOTest {
    
        public static void main(String[] args) throws Exception {
            Selector selector = Selector.open();
            ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
            serverChannel1.bind(new InetSocketAddress(8080));
            serverChannel1.configureBlocking(false);
            serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
    
            ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
            serverChannel2.bind(new InetSocketAddress(8081));
            serverChannel2.configureBlocking(false);
    
    
            Thread thread1 = new Thread(() -> {
                while (true) {
                    try {
                        selector.select();
                        Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                        while (keys.hasNext()) {
                            SelectionKey key = keys.next();
                            keys.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("Accepted connection from: " + clientChannel.getRemoteAddress());
                            } else if (key.isReadable()) {
                                SocketChannel clientChannel = (SocketChannel) key.channel();
                                ByteBuffer buffer = ByteBuffer.allocate(256);
    
                                int bytesRead = clientChannel.read(buffer);
                                if (bytesRead == -1) {
                                    clientChannel.close();
                                    System.out.println("Client disconnected.");
                                } else {
                                    buffer.flip();
                                    String message = new String(buffer.array(), 0, buffer.limit());
                                    System.out.println("Received: " + message);
    
                                    buffer.rewind();
                                    clientChannel.write(buffer);
                                }
                            }
                        }
                    } catch (IOException e) {}
                }
            });
    
            Thread thread2 = new Thread(() -> {
                try {
                    Thread.sleep(1000);
                    selector.wakeup();
                    serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
                } catch (Exception e) {}
            });
    
            thread1.start();
            thread2.start();
    
        }
    }

     

     

    Thread thread2 에서 selector.wakeup() 이 호출되지 않는다면, TCP 요청이 수행되더라도 NIO Selector 의 Blocking 이 해제되지 않습니다.

     

    nc -zv localhost 8081

     

    하지만 Thread thread2 에서 selector.wakeup() 이 호출된다면, NIO SelectorBlocking 이 해제되어,

    Selector 는 8081 포트에 바인딩된 socketChannel2 와의 OP_ACCEPT 이벤트 연결을 적용하게 됩니다.

     

    nc -zv localhost 8081
    Accepted connection from: /0:0:0:0:0:0:0:1:60059
    Client disconnected.
    Accepted connection from: /0:0:0:0:0:0:0:1:60060
    Client disconnected.
    Accepted connection from: /0:0:0:0:0:0:0:1:60061
    Client disconnected.
    Accepted connection from: /0:0:0:0:0:0:0:1:60062
    Client disconnected.
    Accepted connection from: /0:0:0:0:0:0:0:1:60067
    Client disconnected.
    Accepted connection from: /0:0:0:0:0:0:0:1:60068
    Client disconnected.
    Accepted connection from: /0:0:0:0:0:0:0:1:60069
    Client disconnected.

     

     

    반응형
Designed by Tistory.