-
[Java NIO] Selector register 이해하기Java/NIO 2024. 6. 11. 06:35728x90반응형
- 목차
들어가며.
Java NIO 의 Selector 는 비동기적으로 여러 채널(Channel)을 모니터링할 수 있는 강력한 도구입니다.
이 중에서도 register 메서드는 특정 채널을 Selector에 등록하고 관심 이벤트를 설정하는 중요한 역할을 합니다.
이 글에서는 register 메서드의 사용법과 작동 원리를 살펴보겠습니다.
Selector와 Channel의 관계.
Selector 는 여러 개의 Channel 을 등록하여, 읽기, 쓰기, 연결 수락 등의 이벤트를 비동기적으로 감지할 수 있습니다.
register 메서드는 Selector 와 Channel 사이의 연결 고리 역할을 합니다.
- Selector: 이벤트를 감지하고, 필요한 처리를 트리거합니다.
- Channel: Selector 에 등록되어 이벤트를 발생시킵니다.
register 메서드 시그니처.
register 메소드의 시그니처는 아래와 같습니다.
SelectionKey register(Selector sel, int ops, Object att);
- sel: Selector 객체로, 이벤트를 감지할 주체입니다.
- ops: 관심 있는 이벤트를 나타내는 정수 값입니다. OP_READ, OP_WRITE, OP_ACCEPT, OP_CONNECT 등이 있습니다.
- att: 채널과 연관된 부가 정보(attachment)입니다. 이 정보는 이후에 이벤트 처리 시 유용하게 활용됩니다.
- Return: SelectionKey 객체를 반환합니다. 이 키는 Selector와 Channel의 연결을 나타내며, 추가 작업 시 사용할 수 있습니다.
Socket System Call 알아보기.
Java NIO Selector 에 대해서 자세히 알아보기 이전에 네트워크 서버를 구축할 때 사용하는 대표적인 시스템 호출(System Call) 에 대해 먼저 알아보겠습니다.
Socket, Bind, Listen, Accept 등에 대한 System Call 에 대한 배경지식이 있다면 Java NIO 의 Selector 에 대해서 깊이 있는 이해가 가능합니다.
socket.
socket System Call 은 네트워크 통신에 필요한 소켓을 생성합니다.
그리고 소켓은 네트워크 통신의 출발점이 되는 파일 디스크립터 역할을 합니다.
대부분의 서버 프로그래밍은 socket 이라는 System Call 의 호출을 기반으로 시작됩니다.
이는 NIC (Network Interface Card) 와 서버 프로그램 사이에 통신을 수행할 Socket 이라는 가상 파일을 생성하는 과정입니다.
Socket System Call 을 통해서 소켓이라는 가상의 파일이 생성되구요.
이를 통해서 네트워크 트래픽이 NIC 를 통해서 서버 프로그램으로 전달될 수 있습니다.
bind.
socket System Call 이후에 호출되는 System Call 은 bind 호출입니다.
bind System Call 은 특정 Port 와 서버 프로그램을 연결하는 역할을 합니다.
그래서 bind(8080) 와 같은 호출을 통해서 localhost:8080 로의 요청이 binding 된 서버 프로그램으로 전달될 수 있습니다.
binding 이 된 시점부터 본격적으로 Socket File 에 네트워크 트래픽의 정보들이 기록될 수 있게 됩니다.
listen.
listen System Call 은 서버 프로그램이 연결된 Socket File 의 네트워크 요청 정보를 읽어들일 수 있게 됩니다.
비로소 서버 프로그램은 클라이언트의 연결 요청을 받을 준비를 합니다.
accept.
accept System Call 은 Socket 으로부터 클라이언트 연결 요청을 가져와 수락합니다.
accept System Call 과정을 통해서 클라이언트와 통신할 새로운 소켓을 반환합니다.
여기서 중요한 점은 최초의 Socket 과 Accept System Call 에 의해서 생성되는 Socket 은 별도의 소켓이라는 점이 중요합니다.
register 의 여러 활용 예시 알아보기.
SelectionKey.OP_ACCEPT
Java NIO 에서 Selector 와 함께 사용되는 상수로, 새로운 연결 요청(Accept 이벤트) 을 감지하는 데 사용됩니다.
이는 Network System Call 중에서 Accept 와 관련됩니다.
SelectionKey.OP_ACCEPT 는 서버와 통신을 위한 새로운 Socket 요청과 이를 수락할 준비를 위한 Event 입니다.
Selector 에 OP_ACCEPT 이벤트를 등록하는 방법은 아래와 같습니다.
package com.westlife.test; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; public class RegisterTest { public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.bind(new InetSocketAddress(8080)); serverChannel.configureBlocking(false); serverChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("Server is listening on port 8080..."); while (true) { 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); } } } } } }
nc -vz localhost 8080 # Connection to localhost port 8080 [tcp/http-alt] succeeded!
SelectionKey.OP_READ
Java NIO Selector 의 OP_READ 는 Selector 에 등록된 Channel 에 데이터가 존재하여 읽을 준비가 된 상태를 모니터링하기 위한 이벤트입니다.
OP_ACCEPT 단계에서 Client - Server 의 통신을 위한 Socket 이 생성 및 등록되게 되는데요.
OP_READ 는 클라이언트의 요청이 Channel 에 추가되고 이를 처리할 준비가 된 상태입니다.
아래의 예시처럼 key.isAcceptable 과정에서 clientChannel 을 생성하고 이를 Selector 와 연결합니다.
그리고 동시에 SelectionKey.OP_READ 이벤트를 등록하였습니다.
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()); }
이렇게 selector 가 clientChannel 의 OP_READ 이벤트 모니터링을 할 수 있도록 등록해두었기 때문에
아래의 else if (key.isReadable()) 코드 블록이 실행될 수 있습니다.
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); } }
반응형'Java > NIO' 카테고리의 다른 글
[Java NIO] Selector Wakeup 알아보기 (0) 2024.06.10 [Java NIO] NIO 는 어떻게 동작할까 ? (DMA, Kernel Space) (0) 2024.01.31