-
[Java NIO] Selector Wakeup 알아보기Java/NIO 2024. 6. 10. 06:32728x90반응형
- 목차
들어가며.
Java NIO(Non-blocking IO) 는 효율적인 네트워크 프로그램을 작성할 수 있는 강력한 도구를 제공합니다.
그중에서도 Selector 는 다중 채널에서 이벤트를 감지하여 효율적으로 비동기 작업을 처리할 수 있도록 도와줍니다.
하지만 Selector를 사용할 때 wakeup() 메서드를 정확히 이해하고 사용하는 것이 중요합니다.
이 글에서는 Selector.wakeup() 이 무엇인지, 왜 필요한지, 그리고 실제로 어떻게 동작하는지를 살펴보겠습니다.
https://westlife0615.tistory.com/891
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 Selector 의 Blocking 이 해제되어,
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.
반응형'Java > NIO' 카테고리의 다른 글
[Java NIO] Selector register 이해하기 (0) 2024.06.11 [Java NIO] NIO 는 어떻게 동작할까 ? (DMA, Kernel Space) (0) 2024.01.31